summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk1
-rw-r--r--tools/dexfuzz/Android.mk37
-rw-r--r--tools/dexfuzz/README130
-rwxr-xr-xtools/dexfuzz/dexfuzz24
-rw-r--r--tools/dexfuzz/manifest.txt1
-rw-r--r--tools/dexfuzz/src/dexfuzz/DexFuzz.java101
-rw-r--r--tools/dexfuzz/src/dexfuzz/ExecutionResult.java111
-rw-r--r--tools/dexfuzz/src/dexfuzz/Log.java78
-rw-r--r--tools/dexfuzz/src/dexfuzz/MutationStats.java74
-rw-r--r--tools/dexfuzz/src/dexfuzz/Options.java434
-rw-r--r--tools/dexfuzz/src/dexfuzz/StreamConsumer.java202
-rw-r--r--tools/dexfuzz/src/dexfuzz/Timer.java70
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/Architecture.java31
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/Arm64InterpreterExecutor.java49
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/Arm64OptimizingBackendExecutor.java49
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/Arm64QuickBackendExecutor.java49
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/ArmInterpreterExecutor.java49
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/ArmOptimizingBackendExecutor.java49
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/ArmQuickBackendExecutor.java49
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/Device.java98
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/Executor.java303
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/Mips64InterpreterExecutor.java47
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/Mips64OptimizingBackendExecutor.java46
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/Mips64QuickBackendExecutor.java46
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/MipsInterpreterExecutor.java46
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/MipsOptimizingBackendExecutor.java46
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/MipsQuickBackendExecutor.java46
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/X86InterpreterExecutor.java46
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/X86OptimizingBackendExecutor.java46
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/X86QuickBackendExecutor.java46
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/X86_64InterpreterExecutor.java46
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/X86_64OptimizingBackendExecutor.java46
-rw-r--r--tools/dexfuzz/src/dexfuzz/executors/X86_64QuickBackendExecutor.java46
-rw-r--r--tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java449
-rw-r--r--tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultiple.java43
-rw-r--r--tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultipleExecute.java51
-rw-r--r--tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultipleNoExecute.java46
-rw-r--r--tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingle.java38
-rw-r--r--tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingleExecute.java38
-rw-r--r--tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingleNoExecute.java33
-rw-r--r--tools/dexfuzz/src/dexfuzz/listeners/BaseListener.java77
-rw-r--r--tools/dexfuzz/src/dexfuzz/listeners/ConsoleLoggerListener.java163
-rw-r--r--tools/dexfuzz/src/dexfuzz/listeners/LogFileListener.java277
-rw-r--r--tools/dexfuzz/src/dexfuzz/listeners/MultiplexerListener.java205
-rw-r--r--tools/dexfuzz/src/dexfuzz/listeners/UniqueProgramTrackerListener.java259
-rw-r--r--tools/dexfuzz/src/dexfuzz/listeners/UpdatingConsoleListener.java108
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/CodeTranslator.java592
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/IdCreator.java804
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/MBranchInsn.java37
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/MInsn.java64
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/MInsnWithData.java37
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/MSwitchInsn.java50
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/MTryBlock.java29
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/MutatableCode.java410
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/Mutation.java46
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/MutationSerializer.java95
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/Program.java602
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/mutators/ArithOpChanger.java290
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/mutators/BranchShifter.java170
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/mutators/CmpBiasChanger.java154
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/mutators/CodeMutator.java136
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/mutators/ConstantValueChanger.java149
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/mutators/ConversionRepeater.java200
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/mutators/FieldFlagChanger.java167
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/mutators/InstructionDeleter.java138
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/mutators/InstructionDuplicator.java104
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/mutators/InstructionSwapper.java159
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/mutators/NewMethodCaller.java186
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/mutators/NonsenseStringPrinter.java162
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/mutators/PoolIndexChanger.java199
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/mutators/RandomInstructionGenerator.java279
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/mutators/SwitchBranchShifter.java175
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/mutators/TryBlockShifter.java208
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/mutators/VRegChanger.java195
-rw-r--r--tools/dexfuzz/src/dexfuzz/program/mutators/ValuePrinter.java266
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/AnnotationElement.java44
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/AnnotationItem.java43
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/AnnotationOffItem.java38
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetItem.java50
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetRefItem.java38
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetRefList.java50
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/AnnotationsDirectoryItem.java101
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/ClassDataItem.java142
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/ClassDefItem.java77
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/CodeItem.java205
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/DebugInfoItem.java47
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/DexRandomAccessFile.java319
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/EncodedAnnotation.java61
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/EncodedArray.java54
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/EncodedArrayItem.java40
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/EncodedCatchHandler.java62
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/EncodedCatchHandlerList.java48
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/EncodedField.java79
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/EncodedMethod.java95
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/EncodedTypeAddrPair.java43
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/EncodedValue.java98
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/FieldAnnotation.java43
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/FieldIdItem.java54
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/HeaderItem.java128
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/Instruction.java584
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/MapItem.java69
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/MapList.java218
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/MethodAnnotation.java43
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/MethodIdItem.java54
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/Offset.java191
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/OffsetTracker.java513
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/Offsettable.java117
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/Opcode.java280
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/OpcodeInfo.java41
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/ParameterAnnotation.java43
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/ProtoIdItem.java51
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/RawDexFile.java390
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/RawDexObject.java53
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/StringDataItem.java88
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/StringIdItem.java40
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/TryItem.java44
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/TypeIdItem.java42
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/TypeItem.java40
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/TypeList.java71
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/AbstractFormat.java72
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsConst.java32
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsPoolIndex.java41
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsTarget.java29
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsVRegs.java25
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format00x.java54
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format1.java54
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format10t.java56
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format10x.java46
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format11n.java66
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format11x.java51
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format12x.java51
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format2.java54
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format20bc.java51
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format20t.java57
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21c.java78
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21h.java67
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21s.java67
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21t.java62
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22b.java68
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22c.java72
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22cs.java72
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22s.java67
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22t.java62
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22x.java52
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format23x.java53
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3.java54
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format30t.java57
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31c.java68
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31i.java67
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31t.java62
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format32x.java53
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35c.java75
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35mi.java55
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35ms.java55
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rc.java68
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rmi.java48
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rms.java48
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format5.java54
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/Format51l.java70
-rw-r--r--tools/dexfuzz/src/dexfuzz/rawdex/formats/RawInsnHelper.java119
160 files changed, 17775 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk
index 370ea02..76c3aa5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -105,6 +105,7 @@ include $(art_path)/imgdiag/Android.mk
include $(art_path)/patchoat/Android.mk
include $(art_path)/dalvikvm/Android.mk
include $(art_path)/tools/Android.mk
+include $(art_path)/tools/dexfuzz/Android.mk
include $(art_path)/sigchainlib/Android.mk
diff --git a/tools/dexfuzz/Android.mk b/tools/dexfuzz/Android.mk
new file mode 100644
index 0000000..d8f5582
--- /dev/null
+++ b/tools/dexfuzz/Android.mk
@@ -0,0 +1,37 @@
+#
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+# --- dexfuzz.jar ----------------
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAR_MANIFEST := manifest.txt
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE := dexfuzz
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# --- dexfuzz script ----------------
+include $(CLEAR_VARS)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE := dexfuzz
+include $(BUILD_SYSTEM)/base_rules.mk
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/dexfuzz $(ACP)
+ @echo "Copy: $(PRIVATE_MODULE) ($@)"
+ $(copy-file-to-new-target)
+ $(hide) chmod 755 $@
diff --git a/tools/dexfuzz/README b/tools/dexfuzz/README
new file mode 100644
index 0000000..c4795f2
--- /dev/null
+++ b/tools/dexfuzz/README
@@ -0,0 +1,130 @@
+DexFuzz
+=======
+
+DexFuzz is primarily a tool for fuzzing DEX files. Fuzzing is the introduction of
+subtle changes ("mutations") to a file to produce a new test case. These test cases
+can be used to test the various modes of execution available to ART (Interpreter,
+Quick compiler, Optimizing compiler) to check for bugs in these modes of execution.
+This is done by differential testing - each test file is executed with each mode of
+execution, and any differences between the resulting outputs may be an indication of
+a bug in one of the modes.
+
+For a wider overview of DexFuzz, see:
+
+http://community.arm.com/groups/android-community/blog/2014/11/26/the-art-of-fuzz-testing
+
+In typical operation, you provide DexFuzz with a set of DEX files that are the "seeds"
+for mutation - e.g. some tests taken from the ART test suite - and point it at an
+ADB-connected Android device, and it will fuzz these seed files, and execute the
+resulting new tests on the Android device.
+
+How to run DexFuzz
+==================
+
+1. Build dexfuzz with mmm tools/dexfuzz from within art/.
+2. Make sure you have an Android device connected via ADB, that is capable of
+ having DEX files pushed to it and executed with the dalvikvm command.
+3. Make sure you're in the Android build environment!
+ (That is, . build/envsetup.sh && lunch)
+4. Create a new directory, and place some DEX files in here. These are the seed files
+ that are mutated to form new tests.
+5. Create a directory on your device that mutated test files can be pushed to and
+ executed in, using dalvikvm. For example, /data/art-test/
+6. If you currently have multiple devices connected via ADB, find out the name of
+ your device using "adb devices -l".
+7. Run this command:
+
+dexfuzz --inputs=<seeds dir> --execute --repeat=<attempts> \
+ --dump-output <combination of ISA(s) and and backend(s)>
+
+You MUST specify one of the following ISAs:
+ --arm
+ --arm64
+ --x86
+ --x86_64
+ --mips
+ --mips64
+
+And also at least two of the following backends:
+ --interpreter
+ --quick
+ --optimizing
+
+Note that if you wanted to test both ARM and ARM64 on an ARM64 device, you can use
+--allarm. Also in this case only one backend is needed, if i.e., you wanted to test
+ARM Quick Backend vs. ARM64 Quick Backend.
+
+Some legal examples:
+ --arm --quick --optimizing
+ --x86 --quick --optimizing --interpreter
+ --allarm --quick
+
+Add in --device=<device name, e.g. device:generic> if you want to specify a device.
+Add in --execute-dir=<dir on device> if you want to specify an execution directory.
+ (The default is /data/art-test/)
+
+As the fuzzer works, you'll see output like:
+
+|-----------------------------------------------------------------|
+|Iterations|VerifyFail|MutateFail|Timed Out |Successful|Divergence|
+|-----------------------------------------------------------------|
+| 48 | 37 | 4 | 0 | 6 | 1 |
+
+Iterations - number of attempts we've made to mutate DEX files.
+VerifyFail - the number of mutated files that ended up failing to verify, either
+ on the host, or the target.
+MutateFail - because mutation is a random process, and has attempt thresholds to
+ avoid attempting to mutate a file indefinitely, it is possible that
+ an attempt to mutate a file doesn't actually mutate it. This counts
+ those occurrences.
+Timed Out - mutated files that timed out for one or more backends.
+ Current timeouts are:
+ Quick - 5 seconds
+ Optimizing - 5 seconds
+ Intepreter - 30 seconds
+ (use --short-timeouts to set all backends to 2 seconds.)
+Successful - mutated files that executed and all backends agreed on the resulting
+ output. NB: if all backends crashed with the same output, this would
+ be considered a success - proper detection of crashes is still to come.
+Divergence - mutated files that executed and some backend disagreed about the
+ resulting output. Divergent programs are run multiple times with a
+ single backend, to check if they diverge from themselves, and these are
+ not included in the count. If multiple architectures are being used
+ (ARM/ARM64), and the divergences align with different architectures,
+ these are also not included in the count.
+
+8. Check report.log for the full report, including input file and RNG seed for each
+ test program. This allows you to recreate a bad program with, e.g.:
+
+dexfuzz --input=<input file> --seed=<seed value>
+
+Check dexfuzz --help for the full list of options.
+
+NOTE: DEX files with unicode strings are not fully supported yet, and DEX files with
+JNI elements are not supported at all currently.
+
+Mutation Likelihoods
+====================
+
+Each bytecode mutation has a chance out of 100% of firing. Following is the listing
+of each mutation's probability. If you wish to easily adjust these values, copy
+these values into a file called likelihoods.txt, and run dexfuzz with
+--likelihoods=likelihoods.txt.
+
+ArithOpChanger 75
+BranchShifter 30
+CmpBiasChanger 30
+ConstantValueChanger 70
+ConversionRepeater 50
+FieldFlagChanger 40
+InstructionDeleter 40
+InstructionDuplicator 80
+InstructionSwapper 80
+NewMethodCaller 10
+NonsenseStringPrinter 10
+PoolIndexChanger 30
+RandomInstructionGenerator 30
+SwitchBranchShifter 30
+TryBlockShifter 40
+ValuePrinter 40
+VRegChanger 60
diff --git a/tools/dexfuzz/dexfuzz b/tools/dexfuzz/dexfuzz
new file mode 100755
index 0000000..cd47008
--- /dev/null
+++ b/tools/dexfuzz/dexfuzz
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Wrapper script for calling dexfuzz.jar.
+#
+DEBUG=
+#DEBUG="-Xdebug -Xrunjdwp:transport=dt_socket,address=127.0.0.1:8888,server=y,suspend=n -XX:+HeapDumpOnOutOfMemoryError -ea"
+
+java ${DEBUG} -jar ${ANDROID_HOST_OUT}/framework/dexfuzz.jar "$@"
diff --git a/tools/dexfuzz/manifest.txt b/tools/dexfuzz/manifest.txt
new file mode 100644
index 0000000..9e4c214
--- /dev/null
+++ b/tools/dexfuzz/manifest.txt
@@ -0,0 +1 @@
+Main-Class: dexfuzz.DexFuzz
diff --git a/tools/dexfuzz/src/dexfuzz/DexFuzz.java b/tools/dexfuzz/src/dexfuzz/DexFuzz.java
new file mode 100644
index 0000000..2fb9663
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/DexFuzz.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz;
+
+import dexfuzz.fuzzers.Fuzzer;
+import dexfuzz.fuzzers.FuzzerMultipleExecute;
+import dexfuzz.fuzzers.FuzzerMultipleNoExecute;
+import dexfuzz.fuzzers.FuzzerSingleExecute;
+import dexfuzz.fuzzers.FuzzerSingleNoExecute;
+import dexfuzz.listeners.BaseListener;
+import dexfuzz.listeners.ConsoleLoggerListener;
+import dexfuzz.listeners.LogFileListener;
+import dexfuzz.listeners.MultiplexerListener;
+import dexfuzz.listeners.UniqueProgramTrackerListener;
+import dexfuzz.listeners.UpdatingConsoleListener;
+
+/**
+ * Entrypoint class for dexfuzz.
+ */
+public class DexFuzz {
+ private static int majorVersion = 1;
+ private static int minorVersion = 0;
+ private static int seedChangeVersion = 0;
+
+ /**
+ * Entrypoint to dexfuzz.
+ */
+ public static void main(String[] args) {
+ // Report the version number, which should be incremented every time something will cause
+ // the same input seed to produce a different result than before.
+ Log.always(String.format("DexFuzz v%d.%d.%d",
+ majorVersion, minorVersion, seedChangeVersion));
+ Log.always("");
+
+ if (!Options.readOptions(args)) {
+ Log.error("Failed to validate options.");
+ Options.usage();
+ }
+
+ // Create the Listener, which will listen for events and report them.
+ BaseListener listener = null;
+ if (Options.repeat > 1 && Options.execute) {
+ // Create a Listener that is responsible for multiple Listeners.
+ MultiplexerListener multipleListener = new MultiplexerListener();
+ multipleListener.setup();
+ // Add the live updating listener, but only if we're not printing out lots of logs.
+ if (!Log.likelyToLog()) {
+ multipleListener.addListener(new UpdatingConsoleListener());
+ } else {
+ // If we are dumping out lots of logs, then use the ConsoleLogger instead.
+ multipleListener.addListener(new ConsoleLoggerListener());
+ }
+ // Add the file logging listener.
+ multipleListener.addListener(new LogFileListener(Options.reportLogFile));
+ // Add the unique program tracker.
+ multipleListener.addListener(new UniqueProgramTrackerListener(Options.uniqueDatabaseFile));
+ listener = multipleListener;
+ } else {
+ // Just use the basic listener.
+ listener = new ConsoleLoggerListener();
+ }
+
+ // Create the Fuzzer that uses a particular strategy for fuzzing.
+ Fuzzer fuzzer = null;
+ if ((Options.repeat > 1) && Options.execute) {
+ fuzzer = new FuzzerMultipleExecute(listener);
+ } else if ((Options.repeat > 1) && !Options.execute) {
+ fuzzer = new FuzzerMultipleNoExecute(listener);
+ } else if ((Options.repeat == 1) && Options.execute) {
+ fuzzer = new FuzzerSingleExecute(listener);
+ } else if ((Options.repeat == 1) && !Options.execute) {
+ fuzzer = new FuzzerSingleNoExecute(listener);
+ } else {
+ Log.errorAndQuit("Invalid options provided, desired fuzzer unknown.");
+ }
+ // TODO: Implement FuzzerFindMinimalMutations.
+ // TODO: Implement FuzzerGenerational.
+
+ // Actually run the Fuzzer.
+ fuzzer.run();
+ fuzzer.printTimingInfo();
+ fuzzer.shutdown();
+
+ // Cleanup the Listener.
+ listener.shutdown();
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/ExecutionResult.java b/tools/dexfuzz/src/dexfuzz/ExecutionResult.java
new file mode 100644
index 0000000..3a8c6cb
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/ExecutionResult.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz;
+
+import java.util.List;
+
+/**
+ * Stores the output of an executed command.
+ */
+public class ExecutionResult {
+ public List<String> output;
+ public List<String> error;
+ public int returnValue;
+
+ private String flattenedOutput;
+ private String flattenedOutputWithNewlines;
+ private String flattenedError;
+ private String flattenedErrorWithNewlines;
+ private String flattenedAll;
+
+ private static final int TIMEOUT_RETURN_VALUE = 124;
+ private static final int SIGABORT_RETURN_VALUE = 134;
+
+ /**
+ * Get only the output, with all lines concatenated together, excluding newline characters.
+ */
+ public String getFlattenedOutput() {
+ if (flattenedOutput == null) {
+ StringBuilder builder = new StringBuilder();
+ for (String line : output) {
+ builder.append(line);
+ }
+ flattenedOutput = builder.toString();
+ }
+ return flattenedOutput;
+ }
+
+ /**
+ * Get only the output, with all lines concatenated together, including newline characters.
+ */
+ public String getFlattenedOutputWithNewlines() {
+ if (flattenedOutputWithNewlines == null) {
+ StringBuilder builder = new StringBuilder();
+ for (String line : output) {
+ builder.append(line).append("\n");
+ }
+ flattenedOutputWithNewlines = builder.toString();
+ }
+ return flattenedOutputWithNewlines;
+ }
+
+ /**
+ * Get only the error, with all lines concatenated together, excluding newline characters.
+ */
+ public String getFlattenedError() {
+ if (flattenedError == null) {
+ StringBuilder builder = new StringBuilder();
+ for (String line : error) {
+ builder.append(line);
+ }
+ flattenedError = builder.toString();
+ }
+ return flattenedError;
+ }
+
+ /**
+ * Get only the error, with all lines concatenated together, including newline characters.
+ */
+ public String getFlattenedErrorWithNewlines() {
+ if (flattenedErrorWithNewlines == null) {
+ StringBuilder builder = new StringBuilder();
+ for (String line : error) {
+ builder.append(line).append("\n");
+ }
+ flattenedErrorWithNewlines = builder.toString();
+ }
+ return flattenedErrorWithNewlines;
+ }
+
+ /**
+ * Get both the output and error, concatenated together, excluding newline characters.
+ */
+ public String getFlattenedAll() {
+ if (flattenedAll == null) {
+ flattenedAll = getFlattenedOutput() + getFlattenedError();
+ }
+ return flattenedAll;
+ }
+
+ public boolean isTimeout() {
+ return (returnValue == TIMEOUT_RETURN_VALUE);
+ }
+
+ public boolean isSigabort() {
+ return (returnValue == SIGABORT_RETURN_VALUE);
+ }
+} \ No newline at end of file
diff --git a/tools/dexfuzz/src/dexfuzz/Log.java b/tools/dexfuzz/src/dexfuzz/Log.java
new file mode 100644
index 0000000..853550b
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/Log.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz;
+
+/**
+ * Provides access to the logging facilities of dexfuzz.
+ */
+public class Log {
+ private static LogTag threshold = LogTag.ERROR;
+
+ // Disable the constructor for this class.
+ private Log() { }
+
+ public static enum LogTag {
+ DEBUG,
+ INFO,
+ WARN,
+ ERROR,
+ ALWAYS
+ }
+
+ public static void setLoggingLevel(LogTag tag) {
+ threshold = tag;
+ }
+
+ public static boolean likelyToLog() {
+ return (threshold.ordinal() < LogTag.ERROR.ordinal());
+ }
+
+ public static void debug(String msg) {
+ log(LogTag.DEBUG, msg);
+ }
+
+ public static void info(String msg) {
+ log(LogTag.INFO, msg);
+ }
+
+ public static void warn(String msg) {
+ log(LogTag.WARN, msg);
+ }
+
+ public static void error(String msg) {
+ log(LogTag.ERROR, msg);
+ }
+
+ public static void always(String msg) {
+ System.out.println(msg);
+ }
+
+ private static void log(LogTag tag, String msg) {
+ if (tag.ordinal() >= threshold.ordinal()) {
+ System.out.println("[" + tag.toString() + "] " + msg);
+ }
+ }
+
+ /**
+ * Reports error and then terminates the program.
+ */
+ public static void errorAndQuit(String msg) {
+ error(msg);
+ // TODO: Signal sleeping threads.
+ System.exit(1);
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/MutationStats.java b/tools/dexfuzz/src/dexfuzz/MutationStats.java
new file mode 100644
index 0000000..c65b4f2
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/MutationStats.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A wrapper for a dictionary tracking what mutations have been performed.
+ */
+public class MutationStats {
+
+ public static class StatNotFoundException extends RuntimeException {
+ private static final long serialVersionUID = -7038515184655168470L;
+ }
+
+ private Map<String,Long> stats;
+ private List<String> statsOrder;
+
+ public MutationStats() {
+ stats = new HashMap<String,Long>();
+ statsOrder = new ArrayList<String>();
+ }
+
+ public void incrementStat(String statName) {
+ increaseStat(statName, 1);
+ }
+
+ /**
+ * Increase the named stat by the specified amount.
+ */
+ public void increaseStat(String statName, long amt) {
+ if (!stats.containsKey(statName)) {
+ stats.put(statName, 0L);
+ statsOrder.add(statName);
+ }
+ stats.put(statName, stats.get(statName) + amt);
+ }
+
+ /**
+ * Get a string representing the collected stats - looks like a JSON dictionary.
+ */
+ public String getStatsString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("{");
+ boolean first = true;
+ for (String statName : statsOrder) {
+ if (!first) {
+ builder.append(", ");
+ } else {
+ first = false;
+ }
+ builder.append("\"").append(statName).append("\": ").append(stats.get(statName));
+ }
+ builder.append("}");
+ return builder.toString();
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/Options.java b/tools/dexfuzz/src/dexfuzz/Options.java
new file mode 100644
index 0000000..1ae7b5e
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/Options.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz;
+
+import dexfuzz.Log.LogTag;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Stores options for dexfuzz.
+ */
+public class Options {
+ /**
+ * Constructor has been disabled for this class, which should only be used statically.
+ */
+ private Options() { }
+
+ // KEY VALUE OPTIONS
+ public static final List<String> inputFileList = new ArrayList<String>();
+ public static String outputFile = "";
+ public static long rngSeed = -1;
+ public static boolean usingProvidedSeed = false;
+ public static int methodMutations = 3;
+ public static int minMethods = 2;
+ public static int maxMethods = 10;
+ public static final Map<String,Integer> mutationLikelihoods = new HashMap<String,Integer>();
+ public static String executeClass = "Main";
+ public static String deviceName = "";
+ public static boolean usingSpecificDevice = false;
+ public static int repeat = 1;
+ public static String executeDirectory = "/data/art-test";
+ public static String dumpMutationsFile = "mutations.dump";
+ public static String loadMutationsFile = "mutations.dump";
+ public static String reportLogFile = "report.log";
+ public static String uniqueDatabaseFile = "unique_progs.db";
+
+ // FLAG OPTIONS
+ public static boolean execute;
+ public static boolean local;
+ public static boolean noBootImage;
+ public static boolean useInterpreter;
+ public static boolean useQuick;
+ public static boolean useOptimizing;
+ public static boolean useArchArm;
+ public static boolean useArchArm64;
+ public static boolean useArchX86;
+ public static boolean useArchX86_64;
+ public static boolean useArchMips;
+ public static boolean useArchMips64;
+ public static boolean skipHostVerify;
+ public static boolean shortTimeouts;
+ public static boolean dumpOutput;
+ public static boolean dumpVerify;
+ public static boolean mutateLimit;
+ public static boolean reportUnique;
+ public static boolean skipMutation;
+ public static boolean dumpMutations;
+ public static boolean loadMutations;
+
+ /**
+ * Print out usage information about dexfuzz, and then exit.
+ */
+ public static void usage() {
+ Log.always("DexFuzz Usage:");
+ Log.always(" --input=<file> : Seed DEX file to be fuzzed");
+ Log.always(" (Can specify multiple times.)");
+ Log.always(" --inputs=<file> : Directory containing DEX files to be fuzzed.");
+ Log.always(" --output=<file> : Output DEX file to be produced");
+ Log.always("");
+ Log.always(" --execute : Execute the resulting fuzzed program");
+ Log.always(" --local : Execute on host (Not available yet.)");
+ Log.always(" --device=<device> : Execute on an ADB-connected-device, where <device> is");
+ Log.always(" the argument given to adb -s. Default execution mode.");
+ Log.always(" --execute-dir=<dir> : Push tests to this directory to execute them.");
+ Log.always(" (Default: /data/art-test)");
+ Log.always(" --no-boot-image : Use this flag when boot.art is not available.");
+ Log.always(" --skip-host-verify : When executing, skip host-verification stage");
+ Log.always(" --execute-class=<c> : When executing, execute this class (default: Main)");
+ Log.always("");
+ Log.always(" --interpreter : Include the Interpreter in comparisons");
+ Log.always(" --quick : Include the Quick Compiler in comparisons");
+ Log.always(" --optimizing : Include the Optimizing Compiler in comparisons");
+ Log.always("");
+ Log.always(" --arm : Include ARM backends in comparisons");
+ Log.always(" --arm64 : Include ARM64 backends in comparisons");
+ Log.always(" --allarm : Short for --arm --arm64");
+ Log.always(" --x86 : Include x86 backends in comparisons");
+ Log.always(" --x86-64 : Include x86-64 backends in comparisons");
+ Log.always(" --mips : Include MIPS backends in comparisons");
+ Log.always(" --mips64 : Include MIPS64 backends in comparisons");
+ Log.always("");
+ Log.always(" --dump-output : Dump outputs of executed programs");
+ Log.always(" --dump-verify : Dump outputs of verification");
+ Log.always(" --repeat=<n> : Fuzz N programs, executing each one.");
+ Log.always(" --short-timeouts : Shorten timeouts (faster; use if");
+ Log.always(" you want to focus on output divergences)");
+ Log.always(" --seed=<seed> : RNG seed to use");
+ Log.always(" --method-mutations=<n> : Maximum number of mutations to perform on each method.");
+ Log.always(" (Default: 3)");
+ Log.always(" --min-methods=<n> : Minimum number of methods to mutate. (Default: 2)");
+ Log.always(" --max-methods=<n> : Maximum number of methods to mutate. (Default: 10)");
+ Log.always(" --one-mutation : Short for --method-mutations=1 ");
+ Log.always(" --min-methods=1 --max-methods=1");
+ Log.always(" --likelihoods=<file> : A file containing a table of mutation likelihoods");
+ Log.always(" --mutate-limit : Mutate only methods whose names end with _MUTATE");
+ Log.always(" --skip-mutation : Do not actually mutate the input, just output it");
+ Log.always(" after parsing");
+ Log.always("");
+ Log.always(" --dump-mutations[=<file>] : Dump an editable set of mutations applied");
+ Log.always(" to <file> (default: mutations.dump)");
+ Log.always(" --load-mutations[=<file>] : Load and apply a set of mutations");
+ Log.always(" from <file> (default: mutations.dump)");
+ Log.always(" --log=<tag> : Set more verbose logging level: DEBUG, INFO, WARN");
+ Log.always(" --report=<file> : Use <file> to report results when using --repeat");
+ Log.always(" (Default: report.log)");
+ Log.always(" --report-unique : Print out information about unique programs generated");
+ Log.always(" --unique-db=<file> : Use <file> store results about unique programs");
+ Log.always(" (Default: unique_progs.db)");
+ Log.always("");
+ System.exit(0);
+ }
+
+ /**
+ * Given a flag option (one that does not feature an =), handle it
+ * accordingly. Report an error and print usage info if the flag is not
+ * recognised.
+ */
+ private static void handleFlagOption(String flag) {
+ if (flag.equals("execute")) {
+ execute = true;
+ } else if (flag.equals("local")) {
+ local = true;
+ } else if (flag.equals("no-boot-image")) {
+ noBootImage = true;
+ } else if (flag.equals("skip-host-verify")) {
+ skipHostVerify = true;
+ } else if (flag.equals("interpreter")) {
+ useInterpreter = true;
+ } else if (flag.equals("quick")) {
+ useQuick = true;
+ } else if (flag.equals("optimizing")) {
+ useOptimizing = true;
+ } else if (flag.equals("arm")) {
+ useArchArm = true;
+ } else if (flag.equals("arm64")) {
+ useArchArm64 = true;
+ } else if (flag.equals("allarm")) {
+ useArchArm = true;
+ useArchArm64 = true;
+ } else if (flag.equals("x86")) {
+ useArchX86 = true;
+ } else if (flag.equals("x86-64")) {
+ useArchX86_64 = true;
+ } else if (flag.equals("mips")) {
+ useArchMips = true;
+ } else if (flag.equals("mips64")) {
+ useArchMips64 = true;
+ } else if (flag.equals("mutate-limit")) {
+ mutateLimit = true;
+ } else if (flag.equals("report-unique")) {
+ reportUnique = true;
+ } else if (flag.equals("dump-output")) {
+ dumpOutput = true;
+ } else if (flag.equals("dump-verify")) {
+ dumpVerify = true;
+ } else if (flag.equals("short-timeouts")) {
+ shortTimeouts = true;
+ } else if (flag.equals("skip-mutation")) {
+ skipMutation = true;
+ } else if (flag.equals("dump-mutations")) {
+ dumpMutations = true;
+ } else if (flag.equals("load-mutations")) {
+ loadMutations = true;
+ } else if (flag.equals("one-mutation")) {
+ methodMutations = 1;
+ minMethods = 1;
+ maxMethods = 1;
+ } else if (flag.equals("help")) {
+ usage();
+ } else {
+ Log.error("Unrecognised flag: --" + flag);
+ usage();
+ }
+ }
+
+ /**
+ * Given a key-value option (one that features an =), handle it
+ * accordingly. Report an error and print usage info if the key is not
+ * recognised.
+ */
+ private static void handleKeyValueOption(String key, String value) {
+ if (key.equals("input")) {
+ inputFileList.add(value);
+ } else if (key.equals("inputs")) {
+ File folder = new File(value);
+ if (folder.listFiles() == null) {
+ Log.errorAndQuit("Specified argument to --inputs is not a directory!");
+ }
+ for (File file : folder.listFiles()) {
+ String inputName = value + "/" + file.getName();
+ Log.always("Adding " + inputName + " to input seed files.");
+ inputFileList.add(inputName);
+ }
+ } else if (key.equals("output")) {
+ outputFile = value;
+ } else if (key.equals("seed")) {
+ rngSeed = Long.parseLong(value);
+ usingProvidedSeed = true;
+ } else if (key.equals("method-mutations")) {
+ methodMutations = Integer.parseInt(value);
+ } else if (key.equals("min-methods")) {
+ minMethods = Integer.parseInt(value);
+ } else if (key.equals("max-methods")) {
+ maxMethods = Integer.parseInt(value);
+ } else if (key.equals("repeat")) {
+ repeat = Integer.parseInt(value);
+ } else if (key.equals("log")) {
+ Log.setLoggingLevel(LogTag.valueOf(value.toUpperCase()));
+ } else if (key.equals("likelihoods")) {
+ setupMutationLikelihoodTable(value);
+ } else if (key.equals("dump-mutations")) {
+ dumpMutations = true;
+ dumpMutationsFile = value;
+ } else if (key.equals("load-mutations")) {
+ loadMutations = true;
+ loadMutationsFile = value;
+ } else if (key.equals("report")) {
+ reportLogFile = value;
+ } else if (key.equals("unique-db")) {
+ uniqueDatabaseFile = value;
+ } else if (key.equals("execute-class")) {
+ executeClass = value;
+ } else if (key.equals("device")) {
+ deviceName = value;
+ usingSpecificDevice = true;
+ } else if (key.equals("execute-dir")) {
+ executeDirectory = value;
+ } else {
+ Log.error("Unrecognised key: --" + key);
+ usage();
+ }
+ }
+
+ private static void setupMutationLikelihoodTable(String tableFilename) {
+ try {
+ BufferedReader reader = new BufferedReader(new FileReader(tableFilename));
+ String line = reader.readLine();
+ while (line != null) {
+ line = line.replaceAll("\\s+", " ");
+ String[] entries = line.split(" ");
+ String name = entries[0].toLowerCase();
+ int likelihood = Integer.parseInt(entries[1]);
+ if (likelihood > 100) {
+ likelihood = 100;
+ }
+ if (likelihood < 0) {
+ likelihood = 0;
+ }
+ mutationLikelihoods.put(name, likelihood);
+ line = reader.readLine();
+ }
+ reader.close();
+ } catch (FileNotFoundException e) {
+ Log.error("Unable to open mutation probability table file: " + tableFilename);
+ } catch (IOException e) {
+ Log.error("Unable to read mutation probability table file: " + tableFilename);
+ }
+ }
+
+ /**
+ * Called by the DexFuzz class during program initialisation to parse
+ * the program's command line arguments.
+ * @return If options were successfully read and validated.
+ */
+ public static boolean readOptions(String[] args) {
+ for (String arg : args) {
+ if (!(arg.startsWith("--"))) {
+ Log.error("Unrecognised option: " + arg);
+ usage();
+ }
+
+ // cut off the --
+ arg = arg.substring(2);
+
+ // choose between a --X=Y option (keyvalue) and a --X option (flag)
+ if (arg.contains("=")) {
+ String[] split = arg.split("=");
+ handleKeyValueOption(split[0], split[1]);
+ } else {
+ handleFlagOption(arg);
+ }
+ }
+
+ return validateOptions();
+ }
+
+ /**
+ * Checks if the current options settings are valid, called after reading
+ * all options.
+ * @return If the options are valid or not.
+ */
+ private static boolean validateOptions() {
+ // Deal with option assumptions.
+ if (inputFileList.isEmpty()) {
+ File seedFile = new File("fuzzingseed.dex");
+ if (seedFile.exists()) {
+ Log.always("Assuming --input=fuzzingseed.dex");
+ inputFileList.add("fuzzingseed.dex");
+ } else {
+ Log.errorAndQuit("No input given, and couldn't find fuzzingseed.dex!");
+ return false;
+ }
+ }
+
+ if (outputFile.equals("")) {
+ Log.always("Assuming --output=fuzzingseed_fuzzed.dex");
+ outputFile = "fuzzingseed_fuzzed.dex";
+ }
+
+
+ if (mutationLikelihoods.isEmpty()) {
+ File likelihoodsFile = new File("likelihoods.txt");
+ if (likelihoodsFile.exists()) {
+ Log.always("Assuming --likelihoods=likelihoods.txt ");
+ setupMutationLikelihoodTable("likelihoods.txt");
+ } else {
+ Log.always("Using default likelihoods (see README for values)");
+ }
+ }
+
+ // Now check for hard failures.
+ if (repeat < 1) {
+ Log.error("--repeat must be at least 1!");
+ return false;
+ }
+ if (usingProvidedSeed && repeat > 1) {
+ Log.error("Cannot use --repeat with --seed");
+ return false;
+ }
+ if (loadMutations && dumpMutations) {
+ Log.error("Cannot both load and dump mutations");
+ return false;
+ }
+ if (repeat == 1 && inputFileList.size() > 1) {
+ Log.error("Must use --repeat if you have provided more than one input");
+ return false;
+ }
+ if (methodMutations < 0) {
+ Log.error("Cannot use --method-mutations with a negative value.");
+ return false;
+ }
+ if (minMethods < 0) {
+ Log.error("Cannot use --min-methods with a negative value.");
+ return false;
+ }
+ if (maxMethods < 0) {
+ Log.error("Cannot use --max-methods with a negative value.");
+ return false;
+ }
+ if (maxMethods < minMethods) {
+ Log.error("Cannot use --max-methods that's smaller than --min-methods");
+ return false;
+ }
+ if (local && usingSpecificDevice) {
+ Log.error("Cannot use --local and --device!");
+ return false;
+ }
+ if (execute) {
+ if (!(useArchArm
+ || useArchArm64
+ || useArchX86
+ || useArchX86_64
+ || useArchMips
+ || useArchMips64)) {
+ Log.error("No architecture to execute on was specified!");
+ return false;
+ }
+ if ((useArchArm || useArchArm64) && (useArchX86 || useArchX86_64)) {
+ Log.error("Did you mean to specify ARM and x86?");
+ return false;
+ }
+ if ((useArchArm || useArchArm64) && (useArchMips || useArchMips64)) {
+ Log.error("Did you mean to specify ARM and MIPS?");
+ return false;
+ }
+ if ((useArchX86 || useArchX86_64) && (useArchMips || useArchMips64)) {
+ Log.error("Did you mean to specify x86 and MIPS?");
+ return false;
+ }
+ int backends = 0;
+ if (useInterpreter) {
+ backends++;
+ }
+ if (useQuick) {
+ backends++;
+ }
+ if (useOptimizing) {
+ backends++;
+ }
+ if (useArchArm && useArchArm64) {
+ // Could just be comparing quick-ARM versus quick-ARM64?
+ backends++;
+ }
+ if (backends < 2) {
+ Log.error("Not enough backends specified! Try --quick --interpreter!");
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/StreamConsumer.java b/tools/dexfuzz/src/dexfuzz/StreamConsumer.java
new file mode 100644
index 0000000..cd93374
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/StreamConsumer.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+
+/**
+ * process.waitFor() can block if its output buffers are not drained.
+ * These threads are used to keep the buffers drained, and provide the final
+ * output once the command has finished executing. Each Executor has its own
+ * output and error StreamConsumers.
+ */
+public class StreamConsumer extends Thread {
+ private List<String> output;
+ private BufferedReader reader;
+
+ private State state;
+
+ private Semaphore workToBeDone;
+ private Semaphore outputIsReady;
+
+ enum State {
+ WAITING,
+ CONSUMING,
+ SHOULD_STOP_CONSUMING,
+ FINISHED,
+ ERROR
+ }
+
+ /**
+ * Create a StreamConsumer, will be immediately ready to start consuming.
+ */
+ public StreamConsumer() {
+ output = new ArrayList<String>();
+ workToBeDone = new Semaphore(0);
+ outputIsReady = new Semaphore(0);
+
+ state = State.WAITING;
+ }
+
+ /**
+ * Executor should call this to provide its StreamConsumers with the Streams
+ * for a Process it is about to call waitFor() on.
+ */
+ public void giveStreamAndStartConsuming(InputStream stream) {
+ output.clear();
+
+ reader = new BufferedReader(new InputStreamReader(stream));
+
+ changeState(State.CONSUMING, State.WAITING);
+
+ // Tell consumer there is work to be done.
+ workToBeDone.release();
+ }
+
+ /**
+ * Executor should call this once its call to waitFor() returns.
+ */
+ public void processFinished() {
+ changeState(State.SHOULD_STOP_CONSUMING, State.CONSUMING);
+ }
+
+ /**
+ * Executor should call this to get the captured output of this StreamConsumer.
+ */
+ public List<String> getOutput() {
+
+ try {
+ // Wait until the output is ready.
+ outputIsReady.acquire();
+ } catch (InterruptedException e) {
+ Log.error("Client of StreamConsumer was interrupted while waiting for output?");
+ return null;
+ }
+
+ // Take a copy of the Strings, so when we call output.clear(), we don't
+ // clear the ExecutionResult's list.
+ List<String> copy = new ArrayList<String>(output);
+ return copy;
+ }
+
+ /**
+ * Executor should call this when we're shutting down.
+ */
+ public void shutdown() {
+ changeState(State.FINISHED, State.WAITING);
+
+ // Tell Consumer there is work to be done (it will check first if FINISHED has been set.)
+ workToBeDone.release();
+ }
+
+ private void consume() {
+ try {
+
+ if (checkState(State.SHOULD_STOP_CONSUMING)) {
+ // Caller already called processFinished() before we even started
+ // consuming. Just get what we can and finish.
+ while (reader.ready()) {
+ output.add(reader.readLine());
+ }
+ } else {
+ // Caller's process is still executing, so just loop and consume.
+ while (checkState(State.CONSUMING)) {
+ Thread.sleep(50);
+ while (reader.ready()) {
+ output.add(reader.readLine());
+ }
+ }
+ }
+
+ if (checkState(State.SHOULD_STOP_CONSUMING)) {
+ changeState(State.WAITING, State.SHOULD_STOP_CONSUMING);
+ } else {
+ Log.error("StreamConsumer stopped consuming, but was not told to?");
+ setErrorState();
+ }
+
+ reader.close();
+
+ } catch (IOException e) {
+ Log.error("StreamConsumer caught IOException while consuming");
+ setErrorState();
+ } catch (InterruptedException e) {
+ Log.error("StreamConsumer caught InterruptedException while consuming");
+ setErrorState();
+ }
+
+ // Tell client of Consumer that the output is ready.
+ outputIsReady.release();
+ }
+
+ @Override
+ public void run() {
+ while (checkState(State.WAITING)) {
+ try {
+ // Wait until there is work to be done
+ workToBeDone.acquire();
+ } catch (InterruptedException e) {
+ Log.error("StreamConsumer caught InterruptedException while waiting for work");
+ setErrorState();
+ break;
+ }
+
+ // Check first if we're done
+ if (checkState(State.FINISHED)) {
+ break;
+ }
+
+ // Make sure we're either supposed to be consuming
+ // or supposed to be finishing up consuming
+ if (!(checkState(State.CONSUMING) || checkState(State.SHOULD_STOP_CONSUMING))) {
+ Log.error("invalid state: StreamConsumer told about work, but not CONSUMING?");
+ Log.error("state was: " + getCurrentState());
+ setErrorState();
+ break;
+ }
+
+ consume();
+ }
+ }
+
+ private synchronized boolean checkState(State expectedState) {
+ return (expectedState == state);
+ }
+
+ private synchronized void changeState(State newState, State previousState) {
+ if (state != previousState) {
+ Log.error("StreamConsumer Unexpected state: " + state + ", expected " + previousState);
+ state = State.ERROR;
+ } else {
+ state = newState;
+ }
+ }
+
+ private synchronized void setErrorState() {
+ state = State.ERROR;
+ }
+
+ private synchronized State getCurrentState() {
+ return state;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/Timer.java b/tools/dexfuzz/src/dexfuzz/Timer.java
new file mode 100644
index 0000000..8979b8a
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/Timer.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz;
+
+import dexfuzz.listeners.BaseListener;
+
+/**
+ * For timing splits of program execution.
+ */
+public class Timer {
+ /**
+ * The name of the timer, the phase of the program it is intended to time.
+ */
+ private String name;
+
+ /**
+ * A point in time remembered when start() is called.
+ */
+ private long startPoint;
+
+ /**
+ * A cumulative count of how much time has elapsed. Updated each time
+ * stop() is called.
+ */
+ private long elapsedTime;
+
+ /**
+ * Initialise a new timer with the provided name.
+ */
+ public Timer(String name) {
+ this.name = name;
+ this.elapsedTime = 0L;
+ }
+
+ /**
+ * Start timing.
+ */
+ public void start() {
+ startPoint = System.currentTimeMillis();
+ }
+
+ /**
+ * Stop timing, update how much time has elapsed.
+ */
+ public void stop() {
+ long endPoint = System.currentTimeMillis();
+ elapsedTime += (endPoint - startPoint);
+ }
+
+ /**
+ * Log the elapsed time this timer has recorded.
+ */
+ public void printTime(BaseListener listener) {
+ listener.handleTiming(name, ((float)elapsedTime) / 1000.0f);
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/Architecture.java b/tools/dexfuzz/src/dexfuzz/executors/Architecture.java
new file mode 100644
index 0000000..5cdabc3
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/Architecture.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+/**
+ * Every Executor must specify an Architecture. It is important that when reduced
+ * to lower case, these match the ISA string that ART would produce. For example,
+ * the architecture directory used for /data/dalvik-cache/${ISA}
+ */
+public enum Architecture {
+ ARM,
+ ARM64,
+ X86,
+ X86_64,
+ MIPS,
+ MIPS64
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/Arm64InterpreterExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/Arm64InterpreterExecutor.java
new file mode 100644
index 0000000..a945283
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/Arm64InterpreterExecutor.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.listeners.BaseListener;
+
+public class Arm64InterpreterExecutor extends Executor {
+
+ public Arm64InterpreterExecutor(BaseListener listener, Device device) {
+ super("ARM64 Interpreter", 30, listener, Architecture.ARM64, device);
+ }
+
+ @Override
+ public void execute(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dalvikvm64 -Xint ");
+ if (device.noBootImageAvailable()) {
+ commandBuilder.append("-Ximage:/data/art-test/core.art -Xnorelocate ");
+ }
+ commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" ");
+ commandBuilder.append(executeClass);
+ executionResult = executeOnDevice(commandBuilder.toString(), true);
+ }
+
+ @Override
+ public void deleteGeneratedOatFile(String programName) {
+ String command = "rm -f /data/dalvik-cache/arm64/" + getOatFileName(programName);
+ executeOnDevice(command, false);
+ }
+
+ @Override
+ public boolean needsCleanCodeCache() {
+ return false;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/Arm64OptimizingBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/Arm64OptimizingBackendExecutor.java
new file mode 100644
index 0000000..2204ba8
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/Arm64OptimizingBackendExecutor.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.listeners.BaseListener;
+
+public class Arm64OptimizingBackendExecutor extends Executor {
+
+ public Arm64OptimizingBackendExecutor(BaseListener listener, Device device) {
+ super("ARM64 Optimizing Backend", 5, listener, Architecture.ARM64, device);
+ }
+
+ @Override
+ public void execute(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dalvikvm64 -Xcompiler-option --compiler-backend=Optimizing ");
+ if (device.noBootImageAvailable()) {
+ commandBuilder.append("-Ximage:/data/art-test/core.art -Xnorelocate ");
+ }
+ commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" ");
+ commandBuilder.append(executeClass);
+ executionResult = executeOnDevice(commandBuilder.toString(), true);
+ }
+
+ @Override
+ public void deleteGeneratedOatFile(String programName) {
+ String command = "rm -f /data/dalvik-cache/arm64/" + getOatFileName(programName);
+ executeOnDevice(command, false);
+ }
+
+ @Override
+ public boolean needsCleanCodeCache() {
+ return true;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/Arm64QuickBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/Arm64QuickBackendExecutor.java
new file mode 100644
index 0000000..55c9c7a
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/Arm64QuickBackendExecutor.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.listeners.BaseListener;
+
+public class Arm64QuickBackendExecutor extends Executor {
+
+ public Arm64QuickBackendExecutor(BaseListener listener, Device device) {
+ super("ARM64 Quick Backend", 5, listener, Architecture.ARM64, device);
+ }
+
+ @Override
+ public void execute(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dalvikvm64 ");
+ if (device.noBootImageAvailable()) {
+ commandBuilder.append("-Ximage:/data/art-test/core.art -Xnorelocate ");
+ }
+ commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" ");
+ commandBuilder.append(executeClass);
+ executionResult = executeOnDevice(commandBuilder.toString(), true);
+ }
+
+ @Override
+ public void deleteGeneratedOatFile(String programName) {
+ String command = "rm -f /data/dalvik-cache/arm64/" + getOatFileName(programName);
+ executeOnDevice(command, false);
+ }
+
+ @Override
+ public boolean needsCleanCodeCache() {
+ return true;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/ArmInterpreterExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/ArmInterpreterExecutor.java
new file mode 100644
index 0000000..68ce2e0
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/ArmInterpreterExecutor.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.listeners.BaseListener;
+
+public class ArmInterpreterExecutor extends Executor {
+
+ public ArmInterpreterExecutor(BaseListener listener, Device device) {
+ super("ARM Interpreter", 30, listener, Architecture.ARM, device);
+ }
+
+ @Override
+ public void execute(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dalvikvm32 -Xint ");
+ if (device.noBootImageAvailable()) {
+ commandBuilder.append("-Ximage:/data/art-test/core.art -Xnorelocate ");
+ }
+ commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" ");
+ commandBuilder.append(executeClass);
+ executionResult = executeOnDevice(commandBuilder.toString(), true);
+ }
+
+ @Override
+ public void deleteGeneratedOatFile(String programName) {
+ String command = "rm -f /data/dalvik-cache/arm/" + getOatFileName(programName);
+ executeOnDevice(command, false);
+ }
+
+ @Override
+ public boolean needsCleanCodeCache() {
+ return false;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/ArmOptimizingBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/ArmOptimizingBackendExecutor.java
new file mode 100644
index 0000000..78cf652
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/ArmOptimizingBackendExecutor.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.listeners.BaseListener;
+
+public class ArmOptimizingBackendExecutor extends Executor {
+
+ public ArmOptimizingBackendExecutor(BaseListener listener, Device device) {
+ super("ARM Optimizing Backend", 5, listener, Architecture.ARM, device);
+ }
+
+ @Override
+ public void execute(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dalvikvm32 -Xcompiler-option --compiler-backend=Optimizing ");
+ if (device.noBootImageAvailable()) {
+ commandBuilder.append("-Ximage:/data/art-test/core.art -Xnorelocate ");
+ }
+ commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" ");
+ commandBuilder.append(executeClass);
+ executionResult = executeOnDevice(commandBuilder.toString(), true);
+ }
+
+ @Override
+ public void deleteGeneratedOatFile(String programName) {
+ String command = "rm -f /data/dalvik-cache/arm/" + getOatFileName(programName);
+ executeOnDevice(command, false);
+ }
+
+ @Override
+ public boolean needsCleanCodeCache() {
+ return true;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/ArmQuickBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/ArmQuickBackendExecutor.java
new file mode 100644
index 0000000..8f026b2
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/ArmQuickBackendExecutor.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.listeners.BaseListener;
+
+public class ArmQuickBackendExecutor extends Executor {
+
+ public ArmQuickBackendExecutor(BaseListener listener, Device device) {
+ super("ARM Quick Backend", 5, listener, Architecture.ARM, device);
+ }
+
+ @Override
+ public void execute(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dalvikvm32 ");
+ if (device.noBootImageAvailable()) {
+ commandBuilder.append("-Ximage:/data/art-test/core.art -Xnorelocate ");
+ }
+ commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" ");
+ commandBuilder.append(executeClass);
+ executionResult = executeOnDevice(commandBuilder.toString(), true);
+ }
+
+ @Override
+ public void deleteGeneratedOatFile(String programName) {
+ String command = "rm -f /data/dalvik-cache/arm/" + getOatFileName(programName);
+ executeOnDevice(command, false);
+ }
+
+ @Override
+ public boolean needsCleanCodeCache() {
+ return true;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/Device.java b/tools/dexfuzz/src/dexfuzz/executors/Device.java
new file mode 100644
index 0000000..8c03103
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/Device.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+/**
+ * Handles execution either on a remote device, or locally.
+ * Currently only remote execution, on an ADB-connected device, is supported.
+ */
+public class Device {
+ private boolean isLocal;
+ private String deviceName;
+ private boolean usingSpecificDevice;
+ private boolean noBootImage;
+
+ /**
+ * The constructor for a local "device". Not yet supported.
+ */
+ public Device() {
+ this.isLocal = true;
+ throw new UnsupportedOperationException("Currently local execution is not supported.");
+ }
+
+ /**
+ * The constructor for an ADB connected device.
+ */
+ public Device(String deviceName, boolean noBootImage) {
+ if (!deviceName.isEmpty()) {
+ this.deviceName = deviceName;
+ this.usingSpecificDevice = true;
+ }
+ this.noBootImage = noBootImage;
+ }
+
+ /**
+ * Get the name that would be provided to adb -s to communicate specifically with this device.
+ */
+ public String getName() {
+ if (isLocal) {
+ return "LOCAL DEVICE";
+ }
+ return deviceName;
+ }
+
+ public boolean isLocal() {
+ return isLocal;
+ }
+
+ /**
+ * Certain AOSP builds of Android may not have a full boot.art built. This will be set if
+ * we use --no-boot-image, and is used by Executors when deciding the arguments for dalvikvm
+ * and dex2oat when performing host-side verification.
+ */
+ public boolean noBootImageAvailable() {
+ return noBootImage;
+ }
+
+ /**
+ * Get the command prefix for this device if we want to use adb shell.
+ */
+ public String getExecutionShellPrefix() {
+ if (isLocal) {
+ return "";
+ }
+ return getExecutionPrefixWithAdb("shell");
+ }
+
+ /**
+ * Get the command prefix for this device if we want to use adb push.
+ */
+ public String getExecutionPushPrefix() {
+ if (isLocal) {
+ return "";
+ }
+ return getExecutionPrefixWithAdb("push");
+ }
+
+ private String getExecutionPrefixWithAdb(String command) {
+ if (usingSpecificDevice) {
+ return String.format("adb -s %s %s ", deviceName, command);
+ } else {
+ return String.format("adb %s ", command);
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/Executor.java b/tools/dexfuzz/src/dexfuzz/executors/Executor.java
new file mode 100644
index 0000000..7cc584d
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/Executor.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.ExecutionResult;
+import dexfuzz.Log;
+import dexfuzz.Options;
+import dexfuzz.StreamConsumer;
+import dexfuzz.listeners.BaseListener;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Base class containing the common methods for executing a particular backend of ART.
+ */
+public abstract class Executor {
+ private String androidHostOut;
+ private String androidProductOut;
+
+ private StreamConsumer outputConsumer;
+ private StreamConsumer errorConsumer;
+
+ protected ExecutionResult executionResult;
+ protected String executeClass;
+
+ // Set by subclasses.
+ protected String name;
+ protected int timeout;
+ protected BaseListener listener;
+ protected String testLocation;
+ protected Architecture architecture;
+ protected Device device;
+
+ protected Executor(String name, int timeout, BaseListener listener, Architecture architecture,
+ Device device) {
+ executeClass = Options.executeClass;
+
+ if (Options.shortTimeouts) {
+ this.timeout = 2;
+ } else {
+ this.timeout = timeout;
+ }
+
+ this.name = name;
+ this.listener = listener;
+ this.architecture = architecture;
+ this.device = device;
+
+ this.testLocation = Options.executeDirectory;
+
+ Map<String, String> envVars = System.getenv();
+ androidProductOut = checkForEnvVar(envVars, "ANDROID_PRODUCT_OUT");
+ androidHostOut = checkForEnvVar(envVars, "ANDROID_HOST_OUT");
+
+ outputConsumer = new StreamConsumer();
+ outputConsumer.start();
+ errorConsumer = new StreamConsumer();
+ errorConsumer.start();
+
+ if (!device.isLocal()) {
+ // Check for ADB.
+ try {
+ ProcessBuilder pb = new ProcessBuilder();
+ pb.command("adb", "devices");
+ Process process = pb.start();
+ int exitValue = process.waitFor();
+ if (exitValue != 0) {
+ Log.errorAndQuit("Problem executing ADB - is it in your $PATH?");
+ }
+ } catch (IOException e) {
+ Log.errorAndQuit("IOException when executing ADB, is it working?");
+ } catch (InterruptedException e) {
+ Log.errorAndQuit("InterruptedException when executing ADB, is it working?");
+ }
+
+ // Check we can run something on ADB.
+ ExecutionResult result = executeOnDevice("true", true);
+ if (result.getFlattenedAll().contains("device not found")) {
+ Log.errorAndQuit("Couldn't connect to specified ADB device: " + device.getName());
+ }
+ }
+ }
+
+ private String checkForEnvVar(Map<String, String> envVars, String key) {
+ if (!envVars.containsKey(key)) {
+ Log.errorAndQuit("Cannot run a fuzzed program if $" + key + " is not set!");
+ }
+ return envVars.get(key);
+ }
+
+ private ExecutionResult executeCommand(String command, boolean captureOutput) {
+ ExecutionResult result = new ExecutionResult();
+
+ Log.info("Executing: " + command);
+
+ try {
+ ProcessBuilder processBuilder = new ProcessBuilder(command.split(" "));
+ processBuilder.environment().put("ANDROID_ROOT", androidHostOut);
+ Process process = processBuilder.start();
+
+ if (captureOutput) {
+ // Give the streams to the StreamConsumers.
+ outputConsumer.giveStreamAndStartConsuming(process.getInputStream());
+ errorConsumer.giveStreamAndStartConsuming(process.getErrorStream());
+ }
+
+ // Wait until the process is done - the StreamConsumers will keep the
+ // buffers drained, so this shouldn't block indefinitely.
+ // Get the return value as well.
+ result.returnValue = process.waitFor();
+
+ Log.info("Return value: " + result.returnValue);
+
+ if (captureOutput) {
+ // Tell the StreamConsumers to stop consuming, and wait for them to finish
+ // so we know we have all of the output.
+ outputConsumer.processFinished();
+ errorConsumer.processFinished();
+ result.output = outputConsumer.getOutput();
+ result.error = errorConsumer.getOutput();
+
+ // Always explicitly indicate the return code in the text output now.
+ // NB: adb shell doesn't actually return exit codes currently, but this will
+ // be useful if/when it does.
+ result.output.add("RETURN CODE: " + result.returnValue);
+ }
+
+ } catch (IOException e) {
+ Log.errorAndQuit("ExecutionResult.execute() caught an IOException");
+ } catch (InterruptedException e) {
+ Log.errorAndQuit("ExecutionResult.execute() caught an InterruptedException");
+ }
+
+ return result;
+ }
+
+ /**
+ * Called by subclass Executors in their execute() implementations.
+ */
+ protected ExecutionResult executeOnDevice(String command, boolean captureOutput) {
+ String timeoutString = "timeout " + timeout + " ";
+ return executeCommand(timeoutString + device.getExecutionShellPrefix() + command,
+ captureOutput);
+ }
+
+ private ExecutionResult pushToDevice(String command) {
+ return executeCommand(device.getExecutionPushPrefix() + command, false);
+ }
+
+ /**
+ * Call this to make sure the StreamConsumer threads are stopped.
+ */
+ public void shutdown() {
+ outputConsumer.shutdown();
+ errorConsumer.shutdown();
+ }
+
+ /**
+ * Called by the Fuzzer after each execution has finished, to clear the results.
+ */
+ public void reset() {
+ executionResult = null;
+ }
+
+ /**
+ * Called by the Fuzzer to verify the mutated program using the host-side dex2oat.
+ */
+ public boolean verifyOnHost(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dex2oat ");
+
+ // This assumes that the Architecture enum's name, when reduced to lower-case,
+ // matches what dex2oat would expect.
+ commandBuilder.append("--instruction-set=").append(architecture.toString().toLowerCase());
+ commandBuilder.append(" --instruction-set-features=default ");
+
+ // Select the correct boot image.
+ commandBuilder.append("--boot-image=").append(androidProductOut);
+ if (device.noBootImageAvailable()) {
+ commandBuilder.append("/data/art-test/core.art ");
+ } else {
+ commandBuilder.append("/system/framework/boot.art ");
+ }
+
+ commandBuilder.append("--oat-file=output.oat ");
+ commandBuilder.append("--android-root=").append(androidHostOut).append(" ");
+ commandBuilder.append("--runtime-arg -classpath ");
+ commandBuilder.append("--runtime-arg ").append(programName).append(" ");
+ commandBuilder.append("--dex-file=").append(programName).append(" ");
+ commandBuilder.append("--compiler-filter=interpret-only --runtime-arg -Xnorelocate ");
+
+ ExecutionResult verificationResult = executeCommand(commandBuilder.toString(), true);
+
+ boolean success = true;
+
+ if (verificationResult.isSigabort()) {
+ listener.handleHostVerificationSigabort(verificationResult);
+ success = false;
+ }
+
+ if (success) {
+ // Search for a keyword that indicates verification was not successful.
+ // TODO: Determine if dex2oat crashed?
+ for (String line : verificationResult.error) {
+ if (line.contains("Verification error")
+ || line.contains("Failure to verify dex file")) {
+ success = false;
+ }
+ if (Options.dumpVerify) {
+ // Strip out the start of the log lines.
+ listener.handleDumpVerify(line.replaceFirst(".*(cc|h):\\d+] ", ""));
+ }
+ }
+ }
+
+ if (!success) {
+ listener.handleFailedHostVerification(verificationResult);
+ }
+
+ executeCommand("rm output.oat", false);
+
+ return success;
+ }
+
+ /**
+ * Called by the Fuzzer to upload the program to the target device.
+ * TODO: Check if we're executing on a local device, and don't do this?
+ */
+ public void uploadToTarget(String programName) {
+ pushToDevice(programName + " " + testLocation);
+ }
+
+ /**
+ * Executor subclasses need to override this, to construct their arguments for dalvikvm
+ * invocation correctly.
+ */
+ public abstract void execute(String programName);
+
+ /**
+ * Executor subclasses need to override this, to delete their generated OAT file correctly.
+ */
+ public abstract void deleteGeneratedOatFile(String programName);
+
+ /**
+ * Executor subclasses need to override this, to report if they need a cleaned code cache.
+ */
+ public abstract boolean needsCleanCodeCache();
+
+ /**
+ * Fuzzer.checkForArchitectureSplit() will use this determine the architecture of the Executor.
+ */
+ public Architecture getArchitecture() {
+ return architecture;
+ }
+
+ /**
+ * Used in each subclass of Executor's deleteGeneratedOatFile() method, to know what to delete.
+ */
+ protected String getOatFileName(String programName) {
+ // Converts e.g. /data/art-test/file.dex to data@art-test@file.dex
+ return (testLocation.replace("/", "@").substring(1) + "@" + programName);
+ }
+
+ /**
+ * Used by the Fuzzer to get result of execution.
+ */
+ public ExecutionResult getResult() {
+ return executionResult;
+ }
+
+ /**
+ * Because dex2oat can accept a program with soft errors on the host, and then fail after
+ * performing hard verification on the target, we need to check if the Executor detected
+ * a target verification failure, before doing anything else with the resulting output.
+ * Used by the Fuzzer.
+ */
+ public boolean verifyOnTarget() {
+ // TODO: Remove this once host-verification can be forced to always fail?
+ if (executionResult.getFlattenedOutput().contains("VerifyError")) {
+ return false;
+ }
+ return true;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/Mips64InterpreterExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/Mips64InterpreterExecutor.java
new file mode 100644
index 0000000..9f27b5e
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/Mips64InterpreterExecutor.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.listeners.BaseListener;
+
+public class Mips64InterpreterExecutor extends Executor {
+
+ public Mips64InterpreterExecutor(BaseListener listener, Device device) {
+ super("MIPS64 Interpreter", 30, listener, Architecture.MIPS64, device);
+ }
+
+ @Override
+ public void execute(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dalvikvm64 -Xint ");
+ commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" ");
+ commandBuilder.append(executeClass);
+ executionResult = executeOnDevice(commandBuilder.toString(), true);
+
+ }
+
+ @Override
+ public void deleteGeneratedOatFile(String programName) {
+ String command = "rm -f /data/dalvik-cache/mips64/" + getOatFileName(programName);
+ executeOnDevice(command, false);
+ }
+
+ @Override
+ public boolean needsCleanCodeCache() {
+ return false;
+ }
+} \ No newline at end of file
diff --git a/tools/dexfuzz/src/dexfuzz/executors/Mips64OptimizingBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/Mips64OptimizingBackendExecutor.java
new file mode 100644
index 0000000..b30240d
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/Mips64OptimizingBackendExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.listeners.BaseListener;
+
+public class Mips64OptimizingBackendExecutor extends Executor {
+
+ public Mips64OptimizingBackendExecutor(BaseListener listener, Device device) {
+ super("MIPS64 Optimizing Backend", 5, listener, Architecture.MIPS64, device);
+ }
+
+ @Override
+ public void execute(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dalvikvm64 -Xcompiler-option --compiler-backend=Optimizing ");
+ commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" ");
+ commandBuilder.append(executeClass);
+ executionResult = executeOnDevice(commandBuilder.toString(), true);
+ }
+
+ @Override
+ public void deleteGeneratedOatFile(String programName) {
+ String command = "rm -f /data/dalvik-cache/mips64/" + getOatFileName(programName);
+ executeOnDevice(command, false);
+ }
+
+ @Override
+ public boolean needsCleanCodeCache() {
+ return true;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/Mips64QuickBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/Mips64QuickBackendExecutor.java
new file mode 100644
index 0000000..42ccd1e
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/Mips64QuickBackendExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.listeners.BaseListener;
+
+public class Mips64QuickBackendExecutor extends Executor {
+
+ public Mips64QuickBackendExecutor(BaseListener listener, Device device) {
+ super("MIPS64 Quick Backend", 5, listener, Architecture.MIPS64, device);
+ }
+
+ @Override
+ public void execute(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dalvikvm64 ");
+ commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" ");
+ commandBuilder.append(executeClass);
+ executionResult = executeOnDevice(commandBuilder.toString(), true);
+ }
+
+ @Override
+ public void deleteGeneratedOatFile(String programName) {
+ String command = "rm -f /data/dalvik-cache/mips64/" + getOatFileName(programName);
+ executeOnDevice(command, false);
+ }
+
+ @Override
+ public boolean needsCleanCodeCache() {
+ return true;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/MipsInterpreterExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/MipsInterpreterExecutor.java
new file mode 100644
index 0000000..524eaa9
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/MipsInterpreterExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.listeners.BaseListener;
+
+public class MipsInterpreterExecutor extends Executor {
+
+ public MipsInterpreterExecutor(BaseListener listener, Device device) {
+ super("MIPS Interpreter", 30, listener, Architecture.MIPS, device);
+ }
+
+ @Override
+ public void execute(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dalvikvm32 -Xint ");
+ commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" ");
+ commandBuilder.append(executeClass);
+ executionResult = executeOnDevice(commandBuilder.toString(), true);
+ }
+
+ @Override
+ public void deleteGeneratedOatFile(String programName) {
+ String command = "rm -f /data/dalvik-cache/mips/" + getOatFileName(programName);
+ executeOnDevice(command, false);
+ }
+
+ @Override
+ public boolean needsCleanCodeCache() {
+ return false;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/MipsOptimizingBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/MipsOptimizingBackendExecutor.java
new file mode 100644
index 0000000..fcc92c8
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/MipsOptimizingBackendExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.listeners.BaseListener;
+
+public class MipsOptimizingBackendExecutor extends Executor {
+
+ public MipsOptimizingBackendExecutor(BaseListener listener, Device device) {
+ super("MIPS Optimizing Backend", 5, listener, Architecture.MIPS, device);
+ }
+
+ @Override
+ public void execute(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dalvikvm32 -Xcompiler-option --compiler-backend=Optimizing ");
+ commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" ");
+ commandBuilder.append(executeClass);
+ executionResult = executeOnDevice(commandBuilder.toString(), true);
+ }
+
+ @Override
+ public void deleteGeneratedOatFile(String programName) {
+ String command = "rm -f /data/dalvik-cache/mips/" + getOatFileName(programName);
+ executeOnDevice(command, false);
+ }
+
+ @Override
+ public boolean needsCleanCodeCache() {
+ return true;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/MipsQuickBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/MipsQuickBackendExecutor.java
new file mode 100644
index 0000000..cb442f9
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/MipsQuickBackendExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.listeners.BaseListener;
+
+public class MipsQuickBackendExecutor extends Executor {
+
+ public MipsQuickBackendExecutor(BaseListener listener, Device device) {
+ super("MIPS Quick Backend", 5, listener, Architecture.MIPS, device);
+ }
+
+ @Override
+ public void execute(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dalvikvm32 ");
+ commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" ");
+ commandBuilder.append(executeClass);
+ executionResult = executeOnDevice(commandBuilder.toString(), true);
+ }
+
+ @Override
+ public void deleteGeneratedOatFile(String programName) {
+ String command = "rm -f /data/dalvik-cache/mips/" + getOatFileName(programName);
+ executeOnDevice(command, false);
+ }
+
+ @Override
+ public boolean needsCleanCodeCache() {
+ return true;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/X86InterpreterExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/X86InterpreterExecutor.java
new file mode 100644
index 0000000..93c14e9
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/X86InterpreterExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.listeners.BaseListener;
+
+public class X86InterpreterExecutor extends Executor {
+
+ public X86InterpreterExecutor(BaseListener listener, Device device) {
+ super("x86 Interpreter", 30, listener, Architecture.X86, device);
+ }
+
+ @Override
+ public void execute(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dalvikvm32 -Xint ");
+ commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" ");
+ commandBuilder.append(executeClass);
+ executionResult = executeOnDevice(commandBuilder.toString(), true);
+ }
+
+ @Override
+ public void deleteGeneratedOatFile(String programName) {
+ String command = "rm -f /data/dalvik-cache/x86/" + getOatFileName(programName);
+ executeOnDevice(command, false);
+ }
+
+ @Override
+ public boolean needsCleanCodeCache() {
+ return false;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/X86OptimizingBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/X86OptimizingBackendExecutor.java
new file mode 100644
index 0000000..b27d5ca
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/X86OptimizingBackendExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.listeners.BaseListener;
+
+public class X86OptimizingBackendExecutor extends Executor {
+
+ public X86OptimizingBackendExecutor(BaseListener listener, Device device) {
+ super("x86 Optimizing Backend", 5, listener, Architecture.X86, device);
+ }
+
+ @Override
+ public void execute(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dalvikvm32 -Xcompiler-option --compiler-backend=Optimizing ");
+ commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" ");
+ commandBuilder.append(executeClass);
+ executionResult = executeOnDevice(commandBuilder.toString(), true);
+ }
+
+ @Override
+ public void deleteGeneratedOatFile(String programName) {
+ String command = "rm -f /data/dalvik-cache/x86/" + getOatFileName(programName);
+ executeOnDevice(command, false);
+ }
+
+ @Override
+ public boolean needsCleanCodeCache() {
+ return true;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/X86QuickBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/X86QuickBackendExecutor.java
new file mode 100644
index 0000000..d8ec217
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/X86QuickBackendExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.listeners.BaseListener;
+
+public class X86QuickBackendExecutor extends Executor {
+
+ public X86QuickBackendExecutor(BaseListener listener, Device device) {
+ super("x86 Quick Backend", 5, listener, Architecture.X86, device);
+ }
+
+ @Override
+ public void execute(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dalvikvm32 ");
+ commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" ");
+ commandBuilder.append(executeClass);
+ executionResult = executeOnDevice(commandBuilder.toString(), true);
+ }
+
+ @Override
+ public void deleteGeneratedOatFile(String programName) {
+ String command = "rm -f /data/dalvik-cache/x86/" + getOatFileName(programName);
+ executeOnDevice(command, false);
+ }
+
+ @Override
+ public boolean needsCleanCodeCache() {
+ return true;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/X86_64InterpreterExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/X86_64InterpreterExecutor.java
new file mode 100644
index 0000000..7497322
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/X86_64InterpreterExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.listeners.BaseListener;
+
+public class X86_64InterpreterExecutor extends Executor {
+
+ public X86_64InterpreterExecutor(BaseListener listener, Device device) {
+ super("x86_64 Interpreter", 30, listener, Architecture.X86_64, device);
+ }
+
+ @Override
+ public void execute(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dalvikvm64 -Xint ");
+ commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" ");
+ commandBuilder.append(executeClass);
+ executionResult = executeOnDevice(commandBuilder.toString(), true);
+ }
+
+ @Override
+ public void deleteGeneratedOatFile(String programName) {
+ String command = "rm -f /data/dalvik-cache/x86_64/" + getOatFileName(programName);
+ executeOnDevice(command, false);
+ }
+
+ @Override
+ public boolean needsCleanCodeCache() {
+ return false;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/X86_64OptimizingBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/X86_64OptimizingBackendExecutor.java
new file mode 100644
index 0000000..a978f73
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/X86_64OptimizingBackendExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.listeners.BaseListener;
+
+public class X86_64OptimizingBackendExecutor extends Executor {
+
+ public X86_64OptimizingBackendExecutor(BaseListener listener, Device device) {
+ super("x86_64 Optimizing Backend", 5, listener, Architecture.X86_64, device);
+ }
+
+ @Override
+ public void execute(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dalvikvm64 -Xcompiler-option --compiler-backend=Optimizing ");
+ commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" ");
+ commandBuilder.append(executeClass);
+ executionResult = executeOnDevice(commandBuilder.toString(), true);
+ }
+
+ @Override
+ public void deleteGeneratedOatFile(String programName) {
+ String command = "rm -f /data/dalvik-cache/x86_64/" + getOatFileName(programName);
+ executeOnDevice(command, false);
+ }
+
+ @Override
+ public boolean needsCleanCodeCache() {
+ return true;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/X86_64QuickBackendExecutor.java b/tools/dexfuzz/src/dexfuzz/executors/X86_64QuickBackendExecutor.java
new file mode 100644
index 0000000..85532d8
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/executors/X86_64QuickBackendExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.executors;
+
+import dexfuzz.listeners.BaseListener;
+
+public class X86_64QuickBackendExecutor extends Executor {
+
+ public X86_64QuickBackendExecutor(BaseListener listener, Device device) {
+ super("x86_64 Quick Backend", 5, listener, Architecture.X86_64, device);
+ }
+
+ @Override
+ public void execute(String programName) {
+ StringBuilder commandBuilder = new StringBuilder();
+ commandBuilder.append("dalvikvm64 ");
+ commandBuilder.append("-cp ").append(testLocation).append("/").append(programName).append(" ");
+ commandBuilder.append(executeClass);
+ executionResult = executeOnDevice(commandBuilder.toString(), true);
+ }
+
+ @Override
+ public void deleteGeneratedOatFile(String programName) {
+ String command = "rm -f /data/dalvik-cache/x86_64/" + getOatFileName(programName);
+ executeOnDevice(command, false);
+ }
+
+ @Override
+ public boolean needsCleanCodeCache() {
+ return true;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java b/tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java
new file mode 100644
index 0000000..4c1acdb
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.fuzzers;
+
+import dexfuzz.Log;
+import dexfuzz.Options;
+import dexfuzz.Timer;
+import dexfuzz.executors.Architecture;
+import dexfuzz.executors.Arm64InterpreterExecutor;
+import dexfuzz.executors.Arm64OptimizingBackendExecutor;
+import dexfuzz.executors.Arm64QuickBackendExecutor;
+import dexfuzz.executors.ArmInterpreterExecutor;
+import dexfuzz.executors.ArmOptimizingBackendExecutor;
+import dexfuzz.executors.ArmQuickBackendExecutor;
+import dexfuzz.executors.Device;
+import dexfuzz.executors.Executor;
+import dexfuzz.executors.Mips64InterpreterExecutor;
+import dexfuzz.executors.Mips64OptimizingBackendExecutor;
+import dexfuzz.executors.Mips64QuickBackendExecutor;
+import dexfuzz.executors.MipsInterpreterExecutor;
+import dexfuzz.executors.MipsOptimizingBackendExecutor;
+import dexfuzz.executors.MipsQuickBackendExecutor;
+import dexfuzz.executors.X86InterpreterExecutor;
+import dexfuzz.executors.X86OptimizingBackendExecutor;
+import dexfuzz.executors.X86QuickBackendExecutor;
+import dexfuzz.executors.X86_64InterpreterExecutor;
+import dexfuzz.executors.X86_64OptimizingBackendExecutor;
+import dexfuzz.executors.X86_64QuickBackendExecutor;
+import dexfuzz.listeners.BaseListener;
+import dexfuzz.program.Mutation;
+import dexfuzz.program.Program;
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.OffsetTracker;
+import dexfuzz.rawdex.RawDexFile;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A particular fuzzing strategy, this class provides the common methods
+ * most fuzzing will involve, and subclasses override the run() method, to
+ * employ a particular strategy.
+ */
+public abstract class Fuzzer {
+ private List<Executor> executors;
+ private OffsetTracker offsetTracker;
+
+ /**
+ * This is the executor that we use to test for self-divergent programs.
+ */
+ private Executor goldenExecutor;
+
+ /*
+ * These two flags are set during fuzz(), and then cleared at the end of execute().
+ */
+ private boolean mutatedSuccessfully;
+ private boolean savedSuccessfully;
+
+ private Timer totalTimer = new Timer("Total Time");
+ private Timer timerDexInput = new Timer("DEX Input");
+ private Timer timerProgGen = new Timer("Program Generation");
+ private Timer timerMutation = new Timer("Mutation Time");
+ private Timer timerDexOutput = new Timer("DEX Output");
+ private Timer timerChecksumCalc = new Timer("Checksum Calculation");
+
+ protected BaseListener listener;
+
+ protected Fuzzer(BaseListener listener) {
+ totalTimer.start();
+ executors = new ArrayList<Executor>();
+ this.listener = listener;
+ }
+
+ public abstract void run();
+
+ protected abstract String getNextInputFilename();
+
+ protected abstract String getNextOutputFilename();
+
+ /**
+ * Call this after fuzzer execution to print out timing results.
+ */
+ public void printTimingInfo() {
+ totalTimer.stop();
+ timerDexInput.printTime(listener);
+ timerProgGen.printTime(listener);
+ timerMutation.printTime(listener);
+ timerDexOutput.printTime(listener);
+ timerChecksumCalc.printTime(listener);
+ totalTimer.printTime(listener);
+ }
+
+ /**
+ * Make sure this is called to correctly shutdown each Executor's StreamConsumers.
+ */
+ public void shutdown() {
+ if (executors != null) {
+ for (Executor executor : executors) {
+ executor.shutdown();
+ }
+ }
+ }
+
+ private void addExecutorsForArchitecture(Device device, Class<? extends Executor> quick,
+ Class<? extends Executor> optimizing, Class<? extends Executor> interpreter) {
+ // NB: Currently QuickBackend MUST come immediately before same arch's Interpreter.
+ // This is because intepreter execution relies on there being an OAT file already
+ // created to produce correct debug information. Otherwise we will see
+ // false-positive divergences.
+ try {
+ if (Options.useQuick) {
+ Constructor<? extends Executor> constructor =
+ quick.getConstructor(BaseListener.class, Device.class);
+ executors.add(constructor.newInstance(listener, device));
+ }
+ if (Options.useOptimizing) {
+ Constructor<? extends Executor> constructor =
+ optimizing.getConstructor(BaseListener.class, Device.class);
+ executors.add(constructor.newInstance(listener, device));
+ }
+ if (Options.useInterpreter) {
+ Constructor<? extends Executor> constructor =
+ interpreter.getConstructor(BaseListener.class, Device.class);
+ executors.add(constructor.newInstance(listener, device));
+ }
+ } catch (NoSuchMethodException e) {
+ Log.errorAndQuit("Executor doesn't have correct constructor.");
+ } catch (InstantiationException e) {
+ Log.errorAndQuit("Executor couldn't be instantiated.");
+ } catch (IllegalAccessException e) {
+ Log.errorAndQuit("Executor couldn't be accessed.");
+ } catch (IllegalArgumentException e) {
+ Log.errorAndQuit("Invalid arguments to instantiation of Executor.");
+ } catch (InvocationTargetException e) {
+ Log.errorAndQuit("Instantiation of Executor threw an Exception!");
+ }
+ }
+
+ protected void addExecutors() {
+ Device device = null;
+ if (Options.local) {
+ device = new Device();
+ } else {
+ device = new Device(Options.deviceName, Options.noBootImage);
+ }
+
+ if (Options.useArchArm64) {
+ addExecutorsForArchitecture(device, Arm64QuickBackendExecutor.class,
+ Arm64OptimizingBackendExecutor.class, Arm64InterpreterExecutor.class);
+ }
+
+ if (Options.useArchArm) {
+ addExecutorsForArchitecture(device, ArmQuickBackendExecutor.class,
+ ArmOptimizingBackendExecutor.class, ArmInterpreterExecutor.class);
+ }
+
+ if (Options.useArchX86_64) {
+ addExecutorsForArchitecture(device, X86_64QuickBackendExecutor.class,
+ X86_64OptimizingBackendExecutor.class, X86_64InterpreterExecutor.class);
+ }
+
+ if (Options.useArchX86) {
+ addExecutorsForArchitecture(device, X86QuickBackendExecutor.class,
+ X86OptimizingBackendExecutor.class, X86InterpreterExecutor.class);
+ }
+
+ if (Options.useArchMips64) {
+ addExecutorsForArchitecture(device, Mips64QuickBackendExecutor.class,
+ Mips64OptimizingBackendExecutor.class, Mips64InterpreterExecutor.class);
+ }
+
+ if (Options.useArchMips) {
+ addExecutorsForArchitecture(device, MipsQuickBackendExecutor.class,
+ MipsOptimizingBackendExecutor.class, MipsInterpreterExecutor.class);
+ }
+
+ // Add the first backend as the golden executor for self-divergence tests.
+ goldenExecutor = executors.get(0);
+ }
+
+ /**
+ * Called from each Fuzzer subclass that we can instantiate. Parses the program, fuzzes it,
+ * and then saves it, if mutation was successful. We can use --skip-mutation to bypass
+ * the mutation phase, if we wanted to verify that a test program itself works.
+ */
+ protected Program fuzz() {
+ String inputFile = getNextInputFilename();
+ Program program = loadProgram(inputFile, null);
+ if (program == null) {
+ Log.errorAndQuit("Problem loading seed file.");
+ }
+ // Mutate the program.
+ if (!Options.skipMutation) {
+ timerMutation.start();
+ program.mutateTheProgram();
+
+ mutatedSuccessfully = program.updateRawDexFile();
+ timerMutation.stop();
+ if (!mutatedSuccessfully) {
+ listener.handleMutationFail();
+ }
+ } else {
+ Log.info("Skipping mutation stage as requested.");
+ mutatedSuccessfully = true;
+ }
+ if (mutatedSuccessfully) {
+ savedSuccessfully = saveProgram(program, getNextOutputFilename());
+ }
+ return program;
+ }
+
+ protected boolean safeToExecute() {
+ return mutatedSuccessfully && savedSuccessfully;
+ }
+
+ protected void execute(Program program) {
+ if (!safeToExecute()) {
+ Log.errorAndQuit("Your Fuzzer subclass called execute() "
+ + "without checking safeToExecute()!");
+ }
+
+ String programName = getNextOutputFilename();
+ boolean verified = true;
+ if (!Options.skipHostVerify) {
+ verified = goldenExecutor.verifyOnHost(programName);
+ }
+ if (verified) {
+ boolean skipAnalysis = false;
+ boolean uploadedToTarget = false;
+ if (!Options.skipHostVerify) {
+ listener.handleSuccessfulHostVerification();
+ }
+ for (Executor executor : executors) {
+ executor.reset();
+ if (!uploadedToTarget) {
+ executor.uploadToTarget(programName);
+ } else {
+ uploadedToTarget = true;
+ }
+ if (executor.needsCleanCodeCache()) {
+ executor.deleteGeneratedOatFile(programName);
+ }
+ executor.execute(programName);
+ if (!executor.verifyOnTarget()) {
+ listener.handleFailedTargetVerification();
+ skipAnalysis = true;
+ break;
+ }
+ // Results are saved in the executors until they reset, usually at the
+ // next iteration.
+ }
+
+ if (!skipAnalysis) {
+ listener.handleSuccessfullyFuzzedFile(programName);
+ analyseResults(program, programName);
+ }
+ }
+ mutatedSuccessfully = false;
+ savedSuccessfully = false;
+ }
+
+ /**
+ * Checks if the different outputs we observed align with different architectures.
+ */
+ private boolean checkForArchitectureSplit(Map<String, List<Executor>> outputMap) {
+ if (outputMap.size() != 2) {
+ // Cannot have a two-way split if we don't have 2 kinds of output.
+ return false;
+ }
+
+ Architecture[] architectures = new Architecture[2];
+ int archIdx = 0;
+
+ // For each kind of output we saw, make sure they all
+ // came from the same architecture.
+ for (List<Executor> executorList : outputMap.values()) {
+ architectures[archIdx] = executorList.get(0).getArchitecture();
+ for (int execIdx = 1; execIdx < executorList.size(); execIdx++) {
+ if (executorList.get(execIdx).getArchitecture() != architectures[archIdx]) {
+ // Not every executor with this output shared the same architecture.
+ return false;
+ }
+ }
+ archIdx++;
+ }
+
+ // Now make sure that the two outputs we saw were different architectures.
+ if (architectures[0] == architectures[1]) {
+ return false;
+ }
+ return true;
+ }
+
+ private boolean checkGoldenExecutorForSelfDivergence(String programName) {
+ // Run golden executor 5 times, make sure it always produces
+ // the same output, otherwise report that it is self-divergent.
+
+ // TODO: Instead, produce a list of acceptable outputs, and see if the divergent
+ // outputs of the backends fall within this set of outputs.
+ String seenOutput = null;
+ for (int i = 0; i < 5; i++) {
+ goldenExecutor.reset();
+ goldenExecutor.execute(programName);
+ String output = goldenExecutor.getResult().getFlattenedOutput();
+ if (seenOutput == null) {
+ seenOutput = output;
+ } else if (!seenOutput.equals(output)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void analyseResults(Program program, String programName) {
+ // Check timeouts.
+ // Construct two lists of executors, those who timed out, and those who did not.
+ // Report if we had some timeouts.
+ List<Executor> timedOut = new ArrayList<Executor>();
+ List<Executor> didNotTimeOut = new ArrayList<Executor>();
+ for (Executor executor : executors) {
+ if (executor.getResult().isTimeout()) {
+ timedOut.add(executor);
+ } else {
+ didNotTimeOut.add(executor);
+ }
+ }
+ if (!timedOut.isEmpty()) {
+ listener.handleTimeouts(timedOut, didNotTimeOut);
+ // Do not bother reporting divergence information.
+ return;
+ }
+
+ // Check divergences.
+ // Construct a map {output1: [executor that produced output1, ...], output2: [...]}
+ // If the map has more than one output, we had divergence, report it.
+ Map<String, List<Executor>> outputMap = new HashMap<String, List<Executor>>();
+ for (Executor executor : executors) {
+ String output = executor.getResult().getFlattenedOutput();
+ if (Options.dumpOutput) {
+ listener.handleDumpOutput(
+ executor.getResult().getFlattenedOutputWithNewlines(), executor);
+ }
+ if (outputMap.containsKey(output)) {
+ outputMap.get(output).add(executor);
+ } else {
+ List<Executor> newList = new ArrayList<Executor>();
+ newList.add(executor);
+ outputMap.put(output, newList);
+ }
+ }
+
+ if (outputMap.size() > 1) {
+ // Report that we had divergence.
+ listener.handleDivergences(outputMap);
+ listener.handleMutations(program.getMutations());
+ // If we found divergences, try running the "golden executor"
+ // a few times in succession, to see if the output it produces is different
+ // from run to run. If so, then we're probably executing something with either:
+ // a) randomness
+ // b) timing-dependent code
+ // c) threads
+ // So we will not consider it a "true" divergence, but still useful?
+ if (checkGoldenExecutorForSelfDivergence(programName)) {
+ listener.handleSelfDivergence();
+ return;
+ }
+ // If we found divergences, try checking if the differences
+ // in outputs align with differences in architectures.
+ // For example, if we have: {Output1: [ARM, ARM], Output2: [ARM64, ARM64]}
+ if (checkForArchitectureSplit(outputMap)) {
+ listener.handleArchitectureSplit();
+ }
+ } else {
+ // No problems with execution.
+ listener.handleSuccess(outputMap);
+ }
+ }
+
+ private Program loadProgram(String inputName, List<Mutation> mutations) {
+ Program program = null;
+ try {
+ DexRandomAccessFile input = new DexRandomAccessFile(inputName, "r");
+ offsetTracker = new OffsetTracker();
+ input.setOffsetTracker(offsetTracker);
+ // Read the raw DexFile
+ RawDexFile rawDexFile = new RawDexFile();
+ timerDexInput.start();
+ rawDexFile.read(input);
+ timerDexInput.stop();
+ input.close();
+ // Create the program view.
+ timerProgGen.start();
+ program = new Program(rawDexFile, mutations, listener);
+ timerProgGen.stop();
+ } catch (FileNotFoundException e) {
+ Log.errorAndQuit("Couldn't open a file called " + inputName);
+ } catch (IOException e) {
+ Log.errorAndQuit("IOException when trying to load a DEX test file!");
+ }
+ return program;
+ }
+
+ private boolean saveProgram(Program program, String outputName) {
+ boolean success = false;
+
+ try {
+ // Write out the results of mutation.
+ DexRandomAccessFile output = new DexRandomAccessFile(outputName, "rw");
+ output.setOffsetTracker(offsetTracker);
+ // Delete the contents of the file, in case it already existed.
+ output.setLength(0);
+ // Write out the file.
+ timerDexOutput.start();
+ program.writeRawDexFile(output);
+ timerDexOutput.stop();
+ // Recalculate the header info.
+ timerChecksumCalc.start();
+ program.updateRawDexFileHeader(output);
+ timerChecksumCalc.stop();
+ output.close();
+ success = true;
+ } catch (FileNotFoundException e) {
+ Log.errorAndQuit("Couldn't open a file called " + outputName);
+ } catch (IOException e) {
+ Log.errorAndQuit("IOException when trying to save a DEX test file!");
+ }
+ return success;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultiple.java b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultiple.java
new file mode 100644
index 0000000..8abaeb0
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultiple.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.fuzzers;
+
+import dexfuzz.Options;
+import dexfuzz.listeners.BaseListener;
+
+/**
+ * Superclass for fuzzing strategies that perform multiple fuzzes, and want
+ * their inputs to come from the input list in a round-robin fashion.
+ */
+public abstract class FuzzerMultiple extends Fuzzer {
+ protected int iterations;
+
+ protected FuzzerMultiple(BaseListener listener) {
+ super(listener);
+ }
+
+ @Override
+ protected String getNextInputFilename() {
+ String inputFile = Options.inputFileList.get(0);
+ if (Options.inputFileList.size() > 1) {
+ int nextIndex = iterations % Options.inputFileList.size();
+ inputFile = Options.inputFileList.get(nextIndex);
+ }
+ listener.handleFuzzingFile(inputFile);
+ return inputFile;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultipleExecute.java b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultipleExecute.java
new file mode 100644
index 0000000..0cf6df7
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultipleExecute.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.fuzzers;
+
+import dexfuzz.Options;
+import dexfuzz.listeners.BaseListener;
+import dexfuzz.program.Program;
+
+/**
+ * Fuzz programs multiple times, testing each.
+ */
+public class FuzzerMultipleExecute extends FuzzerMultiple {
+ public FuzzerMultipleExecute(BaseListener listener) {
+ super(listener);
+ addExecutors();
+ }
+
+ @Override
+ protected String getNextOutputFilename() {
+ // In MultipleExecute, always use the same output.
+ return Options.outputFile;
+ }
+
+ @Override
+ public void run() {
+ // TODO: Test that all seed files execute correctly before they are mutated!
+ for (iterations = 0; iterations < Options.repeat; iterations++) {
+ listener.handleIterationStarted(iterations);
+ Program program = fuzz();
+ if (safeToExecute()) {
+ execute(program);
+ }
+ listener.handleIterationFinished(iterations);
+ }
+ listener.handleSummary();
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultipleNoExecute.java b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultipleNoExecute.java
new file mode 100644
index 0000000..fea8788
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerMultipleNoExecute.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.fuzzers;
+
+import dexfuzz.Options;
+import dexfuzz.listeners.BaseListener;
+
+/**
+ * Fuzz programs multiple times, writing each one to a new DEX file.
+ */
+public class FuzzerMultipleNoExecute extends FuzzerMultiple {
+ public FuzzerMultipleNoExecute(BaseListener listener) {
+ super(listener);
+ }
+
+ @Override
+ protected String getNextOutputFilename() {
+ // In MultipleNoExecute, produce multiple files, each prefixed
+ // with the iteration value.
+ return String.format("%09d_%s", iterations, Options.outputFile);
+ }
+
+ @Override
+ public void run() {
+ for (iterations = 0; iterations < Options.repeat; iterations++) {
+ listener.handleIterationStarted(iterations);
+ fuzz();
+ listener.handleIterationFinished(iterations);
+ }
+ listener.handleSummary();
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingle.java b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingle.java
new file mode 100644
index 0000000..68b47c2
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingle.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.fuzzers;
+
+import dexfuzz.Options;
+import dexfuzz.listeners.BaseListener;
+
+/**
+ * Superclass for fuzzers that fuzz once.
+ */
+public abstract class FuzzerSingle extends Fuzzer {
+ protected FuzzerSingle(BaseListener listener) {
+ super(listener);
+ }
+
+ @Override
+ protected String getNextInputFilename() {
+ return Options.inputFileList.get(0);
+ }
+
+ protected String getNextOutputFilename() {
+ return Options.outputFile;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingleExecute.java b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingleExecute.java
new file mode 100644
index 0000000..de0c7db
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingleExecute.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.fuzzers;
+
+import dexfuzz.listeners.BaseListener;
+import dexfuzz.program.Program;
+
+/**
+ * Fuzz a DEX file once, and test it.
+ */
+public class FuzzerSingleExecute extends FuzzerSingle {
+ public FuzzerSingleExecute(BaseListener listener) {
+ super(listener);
+ addExecutors();
+ }
+
+ @Override
+ public void run() {
+ Program program = fuzz();
+ if (safeToExecute()) {
+ execute(program);
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingleNoExecute.java b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingleNoExecute.java
new file mode 100644
index 0000000..6015284
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/fuzzers/FuzzerSingleNoExecute.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.fuzzers;
+
+import dexfuzz.listeners.BaseListener;
+
+/**
+ * Fuzz a DEX file once, but don't test it.
+ */
+public class FuzzerSingleNoExecute extends FuzzerSingle {
+ public FuzzerSingleNoExecute(BaseListener listener) {
+ super(listener);
+ }
+
+ @Override
+ public void run() {
+ fuzz();
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/listeners/BaseListener.java b/tools/dexfuzz/src/dexfuzz/listeners/BaseListener.java
new file mode 100644
index 0000000..e33fb09
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/listeners/BaseListener.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.listeners;
+
+import dexfuzz.ExecutionResult;
+import dexfuzz.executors.Executor;
+import dexfuzz.program.Mutation;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Base class for Listeners, who are notified about certain events in dexfuzz's execution.
+ */
+public abstract class BaseListener {
+ public void setup() { }
+
+ public void shutdown() { }
+
+ public void handleSuccessfulHostVerification() { }
+
+ public void handleFailedHostVerification(ExecutionResult verificationResult) { }
+
+ public void handleFailedTargetVerification() { }
+
+ public void handleIterationStarted(int iteration) { }
+
+ public void handleIterationFinished(int iteration) { }
+
+ public void handleTimeouts(List<Executor> timedOut, List<Executor> didNotTimeOut) { }
+
+ public void handleDivergences(Map<String, List<Executor>> outputMap) { }
+
+ public void handleFuzzingFile(String inputFile) { }
+
+ public void handleSeed(long seed) { }
+
+ public void handleHostVerificationSigabort(ExecutionResult verificationResult) { }
+
+ public void handleSuccess(Map<String, List<Executor>> outputMap) { }
+
+ public void handleDumpOutput(String outputLine, Executor executor) { }
+
+ public void handleDumpVerify(String verifyLine) { }
+
+ public void handleMutationStats(String statsString) { }
+
+ public void handleTiming(String name, float elapsedTime) { }
+
+ public void handleMutationFail() { }
+
+ public void handleSummary() { }
+
+ public void handleSuccessfullyFuzzedFile(String programName) { }
+
+ public void handleSelfDivergence() { }
+
+ public void handleMessage(String msg) { }
+
+ public void handleMutations(List<Mutation> mutations) { }
+
+ public void handleArchitectureSplit() { }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/listeners/ConsoleLoggerListener.java b/tools/dexfuzz/src/dexfuzz/listeners/ConsoleLoggerListener.java
new file mode 100644
index 0000000..1ea74d9
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/listeners/ConsoleLoggerListener.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.listeners;
+
+import dexfuzz.ExecutionResult;
+import dexfuzz.executors.Executor;
+import dexfuzz.program.Mutation;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Logs output to the console, when not using --repeat.
+ */
+public class ConsoleLoggerListener extends BaseListener {
+ @Override
+ public void setup() {
+
+ }
+
+ @Override
+ public void shutdown() {
+
+ }
+
+ private void logToConsole(String msg) {
+ System.out.println("CONSOLE: " + msg);
+ }
+
+ @Override
+ public void handleSuccessfulHostVerification() {
+ logToConsole("Successful host verification");
+ }
+
+ @Override
+ public void handleFailedHostVerification(ExecutionResult verificationResult) {
+ logToConsole("Failed host verification");
+ }
+
+ @Override
+ public void handleMutations(List<Mutation> mutations) {
+ for (Mutation mutation : mutations) {
+ logToConsole("Applied mutation: " + mutation.toString());
+ }
+ }
+
+ @Override
+ public void handleArchitectureSplit() {
+ logToConsole("Detected architectural split.");
+ }
+
+ @Override
+ public void handleFailedTargetVerification() {
+ logToConsole("Failed target verification");
+ }
+
+ @Override
+ public void handleIterationStarted(int iteration) {
+ logToConsole("Starting iteration " + iteration);
+ }
+
+ @Override
+ public void handleIterationFinished(int iteration) {
+ logToConsole("Finished iteration " + iteration);
+ }
+
+ @Override
+ public void handleTimeouts(List<Executor> timedOut, List<Executor> didNotTimeOut) {
+ logToConsole("Timed out: " + timedOut.size() + " Did not time out: " + didNotTimeOut.size());
+ }
+
+ @Override
+ public void handleDivergences(Map<String, List<Executor>> outputMap) {
+ logToConsole("Got divergences!");
+ int outputCount = 1;
+ for (List<Executor> executors : outputMap.values()) {
+ logToConsole("Output " + outputCount + ":");
+ for (Executor executor : executors) {
+ logToConsole(" " + executor.getName());
+ }
+ outputCount++;
+ }
+ }
+
+ @Override
+ public void handleFuzzingFile(String inputFile) {
+ logToConsole("Fuzzing: " + inputFile);
+ }
+
+ @Override
+ public void handleSeed(long seed) {
+ logToConsole("Seed: " + seed);
+ }
+
+ @Override
+ public void handleHostVerificationSigabort(ExecutionResult verificationResult) {
+ logToConsole("Sigaborted host verification");
+ }
+
+ @Override
+ public void handleSuccessfullyFuzzedFile(String programName) {
+ logToConsole("Program " + programName + " successfully fuzzed.");
+ }
+
+ @Override
+ public void handleSuccess(Map<String, List<Executor>> outputMap) {
+ logToConsole("Execution was successful");
+ }
+
+ @Override
+ public void handleDumpOutput(String outputLine, Executor executor) {
+ logToConsole(executor.getName() + " OUTPUT: " + outputLine);
+ }
+
+ @Override
+ public void handleDumpVerify(String verifyLine) {
+ logToConsole("VERIFY: " + verifyLine);
+ }
+
+ @Override
+ public void handleMutationFail() {
+ logToConsole("DEX file was not mutated");
+ }
+
+ @Override
+ public void handleMutationStats(String statsString) {
+ logToConsole("Mutations performed: " + statsString);
+ }
+
+ @Override
+ public void handleTiming(String name, float elapsedTime) {
+ logToConsole(String.format("'%s': %.3fs", name, elapsedTime));
+ }
+
+ @Override
+ public void handleSummary() {
+ logToConsole("--- SUMMARY ---");
+ }
+
+ @Override
+ public void handleSelfDivergence() {
+ logToConsole("Seen self divergence");
+ }
+
+ @Override
+ public void handleMessage(String msg) {
+ logToConsole(msg);
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/listeners/LogFileListener.java b/tools/dexfuzz/src/dexfuzz/listeners/LogFileListener.java
new file mode 100644
index 0000000..09ee756
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/listeners/LogFileListener.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.listeners;
+
+import dexfuzz.ExecutionResult;
+import dexfuzz.Log;
+import dexfuzz.executors.Executor;
+import dexfuzz.program.Mutation;
+import dexfuzz.program.MutationSerializer;
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Logs events to a file.
+ */
+public class LogFileListener extends BaseListener {
+ private BufferedWriter writer;
+ boolean ready = false;
+
+ long successfulVerification;
+ long failedVerification;
+ long failedMutation;
+ long success;
+ long timedOut;
+ long divergence;
+ long selfDivergent;
+ long architectureSplit;
+ long iterations;
+
+ private String logFile;
+
+ public LogFileListener(String logFile) {
+ this.logFile = logFile;
+ }
+
+ @Override
+ public void setup() {
+ try {
+ writer = new BufferedWriter(new FileWriter(logFile));
+ ready = true;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ try {
+ writer.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ Log.always("Full log in " + logFile);
+ }
+
+ private void write(String msg) {
+ if (!ready) {
+ return;
+ }
+ try {
+ writer.write(msg + "\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void handleSuccessfulHostVerification() {
+ write("Host verification: SUCCESS");
+ successfulVerification++;
+ }
+
+ @Override
+ public void handleFailedHostVerification(ExecutionResult verificationResult) {
+ write("Host verification: FAILED");
+ failedVerification++;
+ }
+
+ @Override
+ public void handleFailedTargetVerification() {
+ write("Target verification: FAILED");
+ failedVerification++;
+ }
+
+ @Override
+ public void handleIterationStarted(int iteration) {
+ write("--> FUZZ " + (iteration + 1));
+ Date now = new Date(System.currentTimeMillis());
+ write("Time started: " + now.toString());
+ iterations++;
+ }
+
+ @Override
+ public void handleTimeouts(List<Executor> timedOut, List<Executor> didNotTimeOut) {
+ write("Some executors timed out.");
+ write("Timed out:");
+ for (Executor executor : timedOut) {
+ write(" " + executor.getName());
+ }
+ if (!didNotTimeOut.isEmpty()) {
+ write("Did not time out:");
+ for (Executor executor : didNotTimeOut) {
+ write(" " + executor.getName());
+ }
+ }
+ this.timedOut++;
+ }
+
+ @Override
+ public void handleDivergences(Map<String, List<Executor>> outputMap) {
+ write("DIVERGENCE between some executors!");
+ int outputCount = 1;
+ for (List<Executor> executors : outputMap.values()) {
+ write("Output " + outputCount + ":");
+ for (Executor executor : executors) {
+ write(" " + executor.getName());
+ }
+ outputCount++;
+ }
+ divergence++;
+
+ // You are probably interested in reading about these divergences while fuzzing
+ // is taking place, so flush the writer now.
+ try {
+ writer.flush();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void handleFuzzingFile(String inputFile) {
+ write("Fuzzing file '" + inputFile + "'");
+ }
+
+ @Override
+ public void handleSeed(long seed) {
+ write("Using " + seed + " for seed.");
+ // Flush the seed as well, so if anything goes wrong we can see what seed lead
+ // to the issue.
+ try {
+ writer.flush();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void handleHostVerificationSigabort(ExecutionResult verificationResult) {
+ write("Host verification: SIGABORTED");
+ }
+
+ @Override
+ public void handleSuccess(Map<String, List<Executor>> outputMap) {
+ write("All executors agreed on result.");
+ success++;
+ }
+
+ @Override
+ public void handleDumpOutput(String outputLine, Executor executor) {
+ write(executor.getName() + " OUTPUT:");
+ write(outputLine);
+ }
+
+ @Override
+ public void handleDumpVerify(String verifyLine) {
+ write("VERIFY: " + verifyLine);
+ }
+
+ @Override
+ public void handleMutationStats(String statsString) {
+ write("Mutation Stats: " + statsString);
+ }
+
+ @Override
+ public void handleTiming(String name, float elapsedTime) {
+ write(String.format("'%s': %.3fs", name, elapsedTime));
+ }
+
+ @Override
+ public void handleMutationFail() {
+ write("Mutation process: FAILED");
+ failedMutation++;
+ }
+
+ @Override
+ public void handleSummary() {
+ write("");
+ write("---+++--- SUMMARY ---+++---");
+ write("Fuzzing attempts: " + iterations);
+ write(" Failed verification: " + failedVerification);
+ write(" Failed mutation: " + failedMutation);
+ write(" Timed out: " + timedOut);
+ write("Successful: " + success);
+ write(" Self divergent: " + selfDivergent);
+ write(" Architecture split: " + architectureSplit);
+ write("Produced divergence: " + divergence);
+
+ long truelyDivergent = divergence - (selfDivergent + architectureSplit);
+ long verified = success + timedOut + truelyDivergent;
+
+ write("");
+
+ float verifiedTotalRatio =
+ (((float) (verified)) / iterations) * 100.0f;
+ write(String.format("Percentage Verified/Total: %.3f%%", verifiedTotalRatio));
+
+ float timedOutVerifiedRatio =
+ (((float) timedOut) / (verified)) * 100.0f;
+ write(String.format("Percentage Timed Out/Verified: %.3f%%", timedOutVerifiedRatio));
+
+ float successfulVerifiedRatio =
+ (((float) success) / (verified)) * 100.0f;
+ write(String.format("Percentage Successful/Verified: %.3f%%", successfulVerifiedRatio));
+
+ float divergentVerifiedRatio =
+ (((float) truelyDivergent) / (verified)) * 100.0f;
+ write(String.format("Percentage Divergent/Verified: %.3f%%", divergentVerifiedRatio));
+
+ write("---+++--- SUMMARY ---+++---");
+ write("");
+ }
+
+ @Override
+ public void handleIterationFinished(int iteration) {
+ write("");
+ }
+
+ @Override
+ public void handleSuccessfullyFuzzedFile(String programName) {
+ write("Successfully fuzzed file '" + programName + "'");
+ }
+
+ @Override
+ public void handleSelfDivergence() {
+ write("Golden Executor was self-divergent!");
+ selfDivergent++;
+ }
+
+ @Override
+ public void handleArchitectureSplit() {
+ write("Divergent outputs align with difference in architectures.");
+ architectureSplit++;
+ }
+
+ @Override
+ public void handleMessage(String msg) {
+ write(msg);
+ }
+
+ @Override
+ public void handleMutations(List<Mutation> mutations) {
+ write("Mutations Report");
+ for (Mutation mutation : mutations) {
+ write(MutationSerializer.getMutationString(mutation));
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/listeners/MultiplexerListener.java b/tools/dexfuzz/src/dexfuzz/listeners/MultiplexerListener.java
new file mode 100644
index 0000000..28ebce7
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/listeners/MultiplexerListener.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.listeners;
+
+import dexfuzz.ExecutionResult;
+import dexfuzz.executors.Executor;
+import dexfuzz.program.Mutation;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Handles situation where multiple Listeners are wanted, passes notifications
+ * onto each Listener it is responsible for.
+ */
+public class MultiplexerListener extends BaseListener {
+
+ private List<BaseListener> listeners;
+
+ @Override
+ public void setup() {
+ listeners = new ArrayList<BaseListener>();
+ }
+
+ public void addListener(BaseListener listener) {
+ listeners.add(listener);
+ listener.setup();
+ }
+
+ @Override
+ public void shutdown() {
+ for (BaseListener listener : listeners) {
+ listener.shutdown();
+ }
+ }
+
+ @Override
+ public void handleSuccessfulHostVerification() {
+ for (BaseListener listener : listeners) {
+ listener.handleSuccessfulHostVerification();
+ }
+ }
+
+ @Override
+ public void handleFailedHostVerification(ExecutionResult verificationResult) {
+ for (BaseListener listener : listeners) {
+ listener.handleFailedHostVerification(verificationResult);
+ }
+ }
+
+ @Override
+ public void handleFailedTargetVerification() {
+ for (BaseListener listener : listeners) {
+ listener.handleFailedTargetVerification();
+ }
+ }
+
+ @Override
+ public void handleIterationStarted(int iteration) {
+ for (BaseListener listener : listeners) {
+ listener.handleIterationStarted(iteration);
+ }
+ }
+
+ @Override
+ public void handleIterationFinished(int iteration) {
+ for (BaseListener listener : listeners) {
+ listener.handleIterationFinished(iteration);
+ }
+ }
+
+ @Override
+ public void handleTimeouts(List<Executor> timedOut, List<Executor> didNotTimeOut) {
+ for (BaseListener listener : listeners) {
+ listener.handleTimeouts(timedOut, didNotTimeOut);
+ }
+ }
+
+ @Override
+ public void handleDivergences(Map<String, List<Executor>> outputMap) {
+ for (BaseListener listener : listeners) {
+ listener.handleDivergences(outputMap);
+ }
+ }
+
+ @Override
+ public void handleFuzzingFile(String inputFile) {
+ for (BaseListener listener : listeners) {
+ listener.handleFuzzingFile(inputFile);
+ }
+ }
+
+ @Override
+ public void handleSeed(long seed) {
+ for (BaseListener listener : listeners) {
+ listener.handleSeed(seed);
+ }
+ }
+
+ @Override
+ public void handleHostVerificationSigabort(ExecutionResult verificationResult) {
+ for (BaseListener listener : listeners) {
+ listener.handleHostVerificationSigabort(verificationResult);
+ }
+ }
+
+ @Override
+ public void handleSuccess(Map<String, List<Executor>> outputMap) {
+ for (BaseListener listener : listeners) {
+ listener.handleSuccess(outputMap);
+ }
+ }
+
+ @Override
+ public void handleDumpOutput(String outputLine, Executor executor) {
+ for (BaseListener listener : listeners) {
+ listener.handleDumpOutput(outputLine, executor);
+ }
+ }
+
+ @Override
+ public void handleDumpVerify(String verifyLine) {
+ for (BaseListener listener : listeners) {
+ listener.handleDumpVerify(verifyLine);
+ }
+ }
+
+ @Override
+ public void handleMutationStats(String statsString) {
+ for (BaseListener listener : listeners) {
+ listener.handleMutationStats(statsString);
+ }
+ }
+
+ @Override
+ public void handleTiming(String name, float elapsedTime) {
+ for (BaseListener listener : listeners) {
+ listener.handleTiming(name, elapsedTime);
+ }
+ }
+
+ @Override
+ public void handleMutationFail() {
+ for (BaseListener listener : listeners) {
+ listener.handleMutationFail();
+ }
+ }
+
+ @Override
+ public void handleSummary() {
+ for (BaseListener listener : listeners) {
+ listener.handleSummary();
+ }
+ }
+
+ @Override
+ public void handleSuccessfullyFuzzedFile(String programName) {
+ for (BaseListener listener : listeners) {
+ listener.handleSuccessfullyFuzzedFile(programName);
+ }
+ }
+
+ @Override
+ public void handleSelfDivergence() {
+ for (BaseListener listener : listeners) {
+ listener.handleSelfDivergence();
+ }
+ }
+
+ @Override
+ public void handleMessage(String msg) {
+ for (BaseListener listener : listeners) {
+ listener.handleMessage(msg);
+ }
+ }
+
+ @Override
+ public void handleMutations(List<Mutation> mutations) {
+ for (BaseListener listener : listeners) {
+ listener.handleMutations(mutations);
+ }
+ }
+
+ @Override
+ public void handleArchitectureSplit() {
+ for (BaseListener listener : listeners) {
+ listener.handleArchitectureSplit();
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/listeners/UniqueProgramTrackerListener.java b/tools/dexfuzz/src/dexfuzz/listeners/UniqueProgramTrackerListener.java
new file mode 100644
index 0000000..affaffc
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/listeners/UniqueProgramTrackerListener.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.listeners;
+
+import dexfuzz.Log;
+import dexfuzz.Options;
+import dexfuzz.executors.Executor;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tracks unique programs and outputs. Also saves divergent programs!
+ */
+public class UniqueProgramTrackerListener extends BaseListener {
+ /**
+ * Map of unique program MD5 sums, mapped to times seen.
+ */
+ private Map<String, Integer> uniquePrograms;
+
+ /**
+ * Map of unique program outputs (MD5'd), mapped to times seen.
+ */
+ private Map<String, Integer> uniqueOutputs;
+
+ /**
+ * Used to remember the seed used to fuzz the fuzzed file, so we can save it with this
+ * seed as a name, if we find a divergence.
+ */
+ private long currentSeed;
+
+ /**
+ * Used to remember the name of the file we've fuzzed, so we can save it if we
+ * find a divergence.
+ */
+ private String fuzzedFile;
+
+ private MessageDigest digest;
+ private String databaseFile;
+
+ /**
+ * Save the database every X number of iterations.
+ */
+ private static final int saveDatabasePeriod = 20;
+
+ public UniqueProgramTrackerListener(String databaseFile) {
+ this.databaseFile = databaseFile;
+ }
+
+ @Override
+ public void handleSeed(long seed) {
+ currentSeed = seed;
+ }
+
+ /**
+ * Given a program filename, calculate the MD5sum of
+ * this program.
+ */
+ private String getMD5SumOfProgram(String programName) {
+ byte[] buf = new byte[256];
+ try {
+ FileInputStream stream = new FileInputStream(programName);
+ boolean done = false;
+ while (!done) {
+ int bytesRead = stream.read(buf);
+ if (bytesRead == -1) {
+ done = true;
+ } else {
+ digest.update(buf);
+ }
+ }
+ stream.close();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return new String(digest.digest());
+ }
+
+ private String getMD5SumOfOutput(String output) {
+ digest.update(output.getBytes());
+ return new String(digest.digest());
+ }
+
+ @SuppressWarnings("unchecked")
+ private void loadUniqueProgsData() {
+ File file = new File(databaseFile);
+ if (!file.exists()) {
+ uniquePrograms = new HashMap<String, Integer>();
+ uniqueOutputs = new HashMap<String, Integer>();
+ return;
+ }
+
+ try {
+ ObjectInputStream objectStream =
+ new ObjectInputStream(new FileInputStream(databaseFile));
+ uniquePrograms = (Map<String, Integer>) objectStream.readObject();
+ uniqueOutputs = (Map<String, Integer>) objectStream.readObject();
+ objectStream.close();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ private void saveUniqueProgsData() {
+ // Since we could potentially stop the program while writing out this DB,
+ // copy the old file beforehand, and then delete it if we successfully wrote out the DB.
+ boolean oldWasSaved = false;
+ File file = new File(databaseFile);
+ if (file.exists()) {
+ try {
+ Process process =
+ Runtime.getRuntime().exec(String.format("cp %1$s %1$s.old", databaseFile));
+ // Shouldn't block, cp shouldn't produce output.
+ process.waitFor();
+ oldWasSaved = true;
+ } catch (IOException exception) {
+ exception.printStackTrace();
+ } catch (InterruptedException exception) {
+ exception.printStackTrace();
+ }
+ }
+
+ // Now write out the DB.
+ boolean success = false;
+ try {
+ ObjectOutputStream objectStream =
+ new ObjectOutputStream(new FileOutputStream(databaseFile));
+ objectStream.writeObject(uniquePrograms);
+ objectStream.writeObject(uniqueOutputs);
+ objectStream.close();
+ success = true;
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ // If we get here, and we successfully wrote out the DB, delete the saved one.
+ if (oldWasSaved && success) {
+ try {
+ Process process =
+ Runtime.getRuntime().exec(String.format("rm %s.old", databaseFile));
+ // Shouldn't block, rm shouldn't produce output.
+ process.waitFor();
+ } catch (IOException exception) {
+ exception.printStackTrace();
+ } catch (InterruptedException exception) {
+ exception.printStackTrace();
+ }
+ } else if (oldWasSaved && !success) {
+ Log.error("Failed to successfully write out the unique programs DB!");
+ Log.error("Old DB should be saved in " + databaseFile + ".old");
+ }
+ }
+
+ private void addToMap(String md5sum, Map<String, Integer> map) {
+ if (map.containsKey(md5sum)) {
+ map.put(md5sum, map.get(md5sum) + 1);
+ } else {
+ map.put(md5sum, 1);
+ }
+ }
+
+ private void saveDivergentProgram() {
+ File before = new File(fuzzedFile);
+ File after = new File(String.format("divergent_programs/%d.dex", currentSeed));
+ boolean success = before.renameTo(after);
+ if (!success) {
+ Log.error("Failed to save divergent program! Does divergent_programs/ exist?");
+ }
+ }
+
+ @Override
+ public void setup() {
+ try {
+ digest = MessageDigest.getInstance("MD5");
+ loadUniqueProgsData();
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void handleIterationFinished(int iteration) {
+ if ((iteration % saveDatabasePeriod) == (saveDatabasePeriod - 1)) {
+ saveUniqueProgsData();
+ }
+ }
+
+ @Override
+ public void handleSuccessfullyFuzzedFile(String programName) {
+ String md5sum = getMD5SumOfProgram(programName);
+ addToMap(md5sum, uniquePrograms);
+
+ fuzzedFile = programName;
+ }
+
+ @Override
+ public void handleDivergences(Map<String, List<Executor>> outputMap) {
+ // Just use the first one.
+ String output = (String) outputMap.keySet().toArray()[0];
+ String md5sum = getMD5SumOfOutput(output);
+ addToMap(md5sum, uniqueOutputs);
+
+ saveDivergentProgram();
+ }
+
+ @Override
+ public void handleSuccess(Map<String, List<Executor>> outputMap) {
+ // There's only one, use it.
+ String output = (String) outputMap.keySet().toArray()[0];
+ String md5sum = getMD5SumOfOutput(output);
+ addToMap(md5sum, uniqueOutputs);
+ }
+
+ @Override
+ public void handleSummary() {
+ if (Options.reportUnique) {
+ Log.always("-- UNIQUE PROGRAM REPORT --");
+ Log.always("Unique Programs Seen: " + uniquePrograms.size());
+ Log.always("Unique Outputs Seen: " + uniqueOutputs.size());
+ Log.always("---------------------------");
+ }
+
+ saveUniqueProgsData();
+ }
+
+}
diff --git a/tools/dexfuzz/src/dexfuzz/listeners/UpdatingConsoleListener.java b/tools/dexfuzz/src/dexfuzz/listeners/UpdatingConsoleListener.java
new file mode 100644
index 0000000..39d1d2f
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/listeners/UpdatingConsoleListener.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.listeners;
+
+import dexfuzz.ExecutionResult;
+import dexfuzz.executors.Executor;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implements the live updating table of results when --repeat is being used.
+ */
+public class UpdatingConsoleListener extends BaseListener {
+ long successfulVerification;
+ long failedVerification;
+ long failedMutation;
+ long success;
+ long timedOut;
+ long divergence;
+ long selfDivergent;
+ long architectureSplit;
+ long iterations;
+
+ @Override
+ public void setup() {
+ System.out.println("|-----------------------------------------------------------------|");
+ System.out.println("|Iterations|VerifyFail|MutateFail|Timed Out |Successful|Divergence|");
+ System.out.println("|-----------------------------------------------------------------|");
+ }
+
+ @Override
+ public void handleSuccessfulHostVerification() {
+ successfulVerification++;
+ }
+
+ @Override
+ public void handleFailedHostVerification(ExecutionResult verificationResult) {
+ failedVerification++;
+ }
+
+ @Override
+ public void handleFailedTargetVerification() {
+ failedVerification++;
+ }
+
+ @Override
+ public void handleIterationStarted(int iteration) {
+ iterations++;
+ }
+
+ @Override
+ public void handleIterationFinished(int iteration) {
+ String output = String.format("| %-9d| %-9d| %-9d| %-9d| %-9d| %-9d|",
+ iterations, failedVerification, failedMutation, timedOut, success,
+ divergence - (selfDivergent + architectureSplit));
+ System.out.print("\r" + output);
+ }
+
+ @Override
+ public void handleTimeouts(List<Executor> timedOut, List<Executor> didNotTimeOut) {
+ this.timedOut++;
+ }
+
+ @Override
+ public void handleDivergences(Map<String, List<Executor>> outputMap) {
+ divergence++;
+ }
+
+ @Override
+ public void handleSelfDivergence() {
+ selfDivergent++;
+ }
+
+ @Override
+ public void handleArchitectureSplit() {
+ architectureSplit++;
+ }
+
+ @Override
+ public void handleSuccess(Map<String, List<Executor>> outputMap) {
+ success++;
+ }
+
+ @Override
+ public void handleMutationFail() {
+ failedMutation++;
+ }
+
+ @Override
+ public void handleSummary() {
+ System.out.println("\n|-----------------------------------------------------------------|");
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/CodeTranslator.java b/tools/dexfuzz/src/dexfuzz/program/CodeTranslator.java
new file mode 100644
index 0000000..650501b
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/CodeTranslator.java
@@ -0,0 +1,592 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program;
+
+import dexfuzz.Log;
+import dexfuzz.rawdex.CodeItem;
+import dexfuzz.rawdex.EncodedCatchHandler;
+import dexfuzz.rawdex.EncodedTypeAddrPair;
+import dexfuzz.rawdex.Instruction;
+import dexfuzz.rawdex.Opcode;
+import dexfuzz.rawdex.TryItem;
+import dexfuzz.rawdex.formats.ContainsTarget;
+import dexfuzz.rawdex.formats.RawInsnHelper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Translates from a CodeItem (the raw list of Instructions) to MutatableCode
+ * (graph of Instructions, using MInsns and subclasses) and vice-versa.
+ */
+public class CodeTranslator {
+
+ /**
+ * Given a raw DEX file's CodeItem, produce a MutatableCode object, that CodeMutators
+ * are designed to operate on.
+ * @param codeItemIdx Used to make sure the correct CodeItem is updated later after mutation.
+ * @return A new MutatableCode object, which contains all relevant information
+ * obtained from the CodeItem.
+ */
+ public MutatableCode codeItemToMutatableCode(Program program, CodeItem codeItem,
+ int codeItemIdx, int mutatableCodeIdx) {
+ Log.debug("Translating CodeItem " + codeItemIdx
+ + " (" + codeItem.meta.methodName + ") to MutatableCode");
+
+ MutatableCode mutatableCode = new MutatableCode(program);
+
+ codeItem.registerMutatableCode(mutatableCode);
+
+ mutatableCode.name = codeItem.meta.methodName;
+ mutatableCode.shorty = codeItem.meta.shorty;
+ mutatableCode.isStatic = codeItem.meta.isStatic;
+
+ mutatableCode.codeItemIdx = codeItemIdx;
+
+ mutatableCode.mutatableCodeIdx = mutatableCodeIdx;
+
+ mutatableCode.registersSize = codeItem.registersSize;
+ mutatableCode.insSize = codeItem.insSize;
+ mutatableCode.outsSize = codeItem.outsSize;
+ mutatableCode.triesSize = codeItem.triesSize;
+
+ // Temporary map from bytecode offset -> instruction.
+ Map<Integer,MInsn> insnLocationMap = new HashMap<Integer,MInsn>();
+
+ List<Instruction> inputInsns = codeItem.insns;
+
+ // Create the MInsns.
+ int loc = 0;
+ for (Instruction insn : inputInsns) {
+ MInsn mInsn = null;
+
+ if (isInstructionSwitch(insn)) {
+ mInsn = new MSwitchInsn();
+ } else if (isInstructionBranch(insn)) {
+ mInsn = new MBranchInsn();
+ } else if (isInstructionFillArrayData(insn)) {
+ mInsn = new MInsnWithData();
+ } else {
+ mInsn = new MInsn();
+ }
+
+ mInsn.insn = insn;
+
+ // Populate the temporary map.
+ insnLocationMap.put(loc, mInsn);
+
+ // Populate the proper list of mutatable instructions.
+ mutatableCode.addInstructionToEnd(mInsn);
+
+ // Calculate the offsets for each instruction.
+ mInsn.location = loc;
+ mInsn.locationUpdated = false;
+
+ loc += mInsn.insn.getSize();
+ }
+
+ // Now make branch/switch instructions point at the right target instructions.
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (mInsn instanceof MSwitchInsn) {
+ readSwitchInstruction((MSwitchInsn) mInsn, insnLocationMap);
+ } else if (mInsn instanceof MInsnWithData) {
+ ContainsTarget containsTarget = (ContainsTarget) mInsn.insn.info.format;
+ int targetLoc = mInsn.location + (int) containsTarget.getTarget(mInsn.insn);
+ ((MInsnWithData)mInsn).dataTarget = insnLocationMap.get(targetLoc);
+ if (((MInsnWithData)mInsn).dataTarget == null) {
+ Log.errorAndQuit("Bad offset calculation in data-target insn");
+ }
+ } else if (mInsn instanceof MBranchInsn) {
+ ContainsTarget containsTarget = (ContainsTarget) mInsn.insn.info.format;
+ int targetLoc = mInsn.location + (int) containsTarget.getTarget(mInsn.insn);
+ ((MBranchInsn)mInsn).target = insnLocationMap.get(targetLoc);
+ if (((MBranchInsn)mInsn).target == null) {
+ Log.errorAndQuit("Bad offset calculation in branch insn");
+ }
+ }
+ }
+
+ // Now create try blocks.
+ if (mutatableCode.triesSize > 0) {
+ readTryBlocks(codeItem, mutatableCode, insnLocationMap);
+ }
+
+ return mutatableCode;
+ }
+
+ /**
+ * Given a MutatableCode item that may have been mutated, update the original CodeItem
+ * correctly, to allow valid DEX to be written back to the output file.
+ */
+ public void mutatableCodeToCodeItem(CodeItem codeItem, MutatableCode mutatableCode) {
+ Log.debug("Translating MutatableCode " + mutatableCode.name
+ + " to CodeItem " + mutatableCode.codeItemIdx);
+
+ // We must first align any data instructions at the end of the code
+ // before we recalculate any offsets.
+ // This also updates their sizes...
+ alignDataInstructions(mutatableCode);
+
+ // Validate that the tracked locations for instructions are valid.
+ // Also mark locations as no longer being updated.
+ int loc = 0;
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (mInsn.insn.justRaw) {
+ // All just_raw instructions need alignment!
+ if ((loc % 2) != 0) {
+ loc++;
+ }
+ }
+ if (mInsn.location != loc) {
+ Log.errorAndQuit(String.format("%s does not have expected location 0x%x",
+ mInsn, loc));
+ }
+ mInsn.locationUpdated = false;
+ loc += mInsn.insn.getSize();
+ }
+
+ // This new list will be attached to the CodeItem at the end...
+ List<Instruction> outputInsns = new LinkedList<Instruction>();
+
+ // Go through our new list of MInsns, adding them to the new
+ // list of instructions that will be attached to the CodeItem.
+ // Also recalculate offsets for branches.
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (mInsn instanceof MSwitchInsn) {
+ updateSwitchInstruction((MSwitchInsn)mInsn, mutatableCode);
+ } else if (mInsn instanceof MInsnWithData) {
+ MInsn target = ((MInsnWithData) mInsn).dataTarget;
+ int dataOffset = target.location - mInsn.location;
+ ContainsTarget containsTarget = (ContainsTarget) mInsn.insn.info.format;
+ containsTarget.setTarget(mInsn.insn, dataOffset);
+ } else if (mInsn instanceof MBranchInsn) {
+ MInsn target = ((MBranchInsn) mInsn).target;
+ int branchOffset = target.location - mInsn.location;
+ ContainsTarget containsTarget = (ContainsTarget) mInsn.insn.info.format;
+ containsTarget.setTarget(mInsn.insn, branchOffset);
+ }
+ outputInsns.add(mInsn.insn);
+ }
+
+ // Calculate the new insns_size.
+ int newInsnsSize = 0;
+ for (Instruction insn : outputInsns) {
+ newInsnsSize += insn.getSize();
+ }
+
+ if (mutatableCode.triesSize > 0) {
+ updateTryBlocks(codeItem, mutatableCode);
+ }
+
+ codeItem.insnsSize = newInsnsSize;
+ codeItem.insns = outputInsns;
+ codeItem.registersSize = mutatableCode.registersSize;
+ codeItem.insSize = mutatableCode.insSize;
+ codeItem.outsSize = mutatableCode.outsSize;
+ codeItem.triesSize = mutatableCode.triesSize;
+ }
+
+ /**
+ * The TryItem specifies an offset into the EncodedCatchHandlerList for a given CodeItem,
+ * but we only have an array of the EncodedCatchHandlers that the List contains.
+ * This function produces a map that offers a way to find out the index into our array,
+ * from the try handler's offset.
+ */
+ private Map<Short,Integer> createTryHandlerOffsetToIndexMap(CodeItem codeItem) {
+ // Create a sorted set of offsets.
+ List<Short> uniqueOffsets = new ArrayList<Short>();
+ for (TryItem tryItem : codeItem.tries) {
+ int index = 0;
+ while (true) {
+ if ((index == uniqueOffsets.size())
+ || (uniqueOffsets.get(index) > tryItem.handlerOff)) {
+ // First condition means we're at the end of the set (or we're inserting
+ // into an empty set)
+ // Second condition means that the offset belongs here
+ // ...so insert it here, pushing the element currently in this position to the
+ // right, if it exists
+ uniqueOffsets.add(index, tryItem.handlerOff);
+ break;
+ } else if (uniqueOffsets.get(index) == tryItem.handlerOff) {
+ // We've already seen it, and we're making a set, not a list.
+ break;
+ } else {
+ // Keep searching.
+ index++;
+ }
+ }
+ }
+ // Now we have an (implicit) index -> offset mapping!
+
+ // Now create the reverse mapping.
+ Map<Short,Integer> offsetIndexMap = new HashMap<Short,Integer>();
+ for (int i = 0; i < uniqueOffsets.size(); i++) {
+ offsetIndexMap.put(uniqueOffsets.get(i), i);
+ }
+
+ return offsetIndexMap;
+ }
+
+ private void readTryBlocks(CodeItem codeItem, MutatableCode mutatableCode,
+ Map<Integer,MInsn> insnLocationMap) {
+ mutatableCode.mutatableTries = new LinkedList<MTryBlock>();
+
+ Map<Short,Integer> offsetIndexMap = createTryHandlerOffsetToIndexMap(codeItem);
+
+ // Read each TryItem into a MutatableTryBlock.
+ for (TryItem tryItem : codeItem.tries) {
+ MTryBlock mTryBlock = new MTryBlock();
+
+ // Get the MInsns that form the start and end of the try block.
+ int startLocation = tryItem.startAddr;
+ mTryBlock.startInsn = insnLocationMap.get(startLocation);
+ int endLocation = tryItem.startAddr + tryItem.insnCount;
+ mTryBlock.endInsn = insnLocationMap.get(endLocation);
+
+ // Sanity checks.
+ if (mTryBlock.startInsn == null) {
+ Log.errorAndQuit(String.format(
+ "Couldn't find a mutatable insn at start offset 0x%x",
+ startLocation));
+ }
+ if (mTryBlock.endInsn == null) {
+ Log.errorAndQuit(String.format(
+ "Couldn't find a mutatable insn at end offset 0x%x",
+ endLocation));
+ }
+
+ // Get the EncodedCatchHandler.
+ int handlerIdx = offsetIndexMap.get(tryItem.handlerOff);
+ EncodedCatchHandler encodedCatchHandler = codeItem.handlers.list[handlerIdx];
+
+ // Do we have a catch all? If so, associate the MInsn that's there.
+ if (encodedCatchHandler.size <= 0) {
+ mTryBlock.catchAllHandler =
+ insnLocationMap.get(encodedCatchHandler.catchAllAddr);
+ // Sanity check.
+ if (mTryBlock.catchAllHandler == null) {
+ Log.errorAndQuit(
+ String.format("Couldn't find a mutatable insn at catch-all offset 0x%x",
+ encodedCatchHandler.catchAllAddr));
+ }
+ }
+ // Do we have explicitly-typed handlers? This will remain empty if not.
+ mTryBlock.handlers = new LinkedList<MInsn>();
+
+ // Associate all the explicitly-typed handlers.
+ for (int i = 0; i < Math.abs(encodedCatchHandler.size); i++) {
+ EncodedTypeAddrPair handler = encodedCatchHandler.handlers[i];
+ MInsn handlerInsn = insnLocationMap.get(handler.addr);
+ // Sanity check.
+ if (handlerInsn == null) {
+ Log.errorAndQuit(String.format(
+ "Couldn't find a mutatable instruction at handler offset 0x%x",
+ handler.addr));
+ }
+ mTryBlock.handlers.add(handlerInsn);
+ }
+
+ // Now finally add the new MutatableTryBlock into this MutatableCode's list!
+ mutatableCode.mutatableTries.add(mTryBlock);
+ }
+ }
+
+ private void updateTryBlocks(CodeItem codeItem, MutatableCode mutatableCode) {
+
+ // TODO: Support ability to add extra try blocks/handlers?
+
+ for (MTryBlock mTryBlock : mutatableCode.mutatableTries) {
+ if (mTryBlock.startInsn.location > mTryBlock.endInsn.location) {
+ // Mutation has put this try block's end insn before its start insn. Fix this.
+ MInsn tempInsn = mTryBlock.startInsn;
+ mTryBlock.startInsn = mTryBlock.endInsn;
+ mTryBlock.endInsn = tempInsn;
+ }
+ }
+
+ // First, manipulate the try blocks if they overlap.
+ for (int i = 0; i < mutatableCode.mutatableTries.size() - 1; i++) {
+ MTryBlock first = mutatableCode.mutatableTries.get(i);
+ MTryBlock second = mutatableCode.mutatableTries.get(i + 1);
+
+ // Do they overlap?
+ if (first.endInsn.location > second.startInsn.location) {
+
+ Log.debug("Found overlap in TryBlocks, moving 2nd TryBlock...");
+ Log.debug("1st TryBlock goes from " + first.startInsn + " to " + first.endInsn);
+ Log.debug("2nd TryBlock goes from " + second.startInsn + " to " + second.endInsn);
+
+ // Find the first instruction that comes after that does not overlap
+ // with the first try block.
+ MInsn newInsn = second.startInsn;
+ int ptr = mutatableCode.getInstructionIndex(newInsn);
+ while (first.endInsn.location > newInsn.location) {
+ ptr++;
+ newInsn = mutatableCode.getInstructionAt(ptr);
+ }
+ second.startInsn = newInsn;
+
+ Log.debug("Now 2nd TryBlock goes from " + second.startInsn + " to " + second.endInsn);
+ }
+ }
+
+ Map<Short,Integer> offsetIndexMap = createTryHandlerOffsetToIndexMap(codeItem);
+
+ int tryItemIdx = 0;
+ for (MTryBlock mTryBlock : mutatableCode.mutatableTries) {
+ TryItem tryItem = codeItem.tries[tryItemIdx];
+
+ tryItem.startAddr = mTryBlock.startInsn.location;
+ tryItem.insnCount =
+ (short) (mTryBlock.endInsn.location - mTryBlock.startInsn.location);
+
+ // Get the EncodedCatchHandler.
+ EncodedCatchHandler encodedCatchHandler =
+ codeItem.handlers.list[offsetIndexMap.get(tryItem.handlerOff)];
+
+ if (encodedCatchHandler.size <= 0) {
+ encodedCatchHandler.catchAllAddr = mTryBlock.catchAllHandler.location;
+ }
+ for (int i = 0; i < Math.abs(encodedCatchHandler.size); i++) {
+ MInsn handlerInsn = mTryBlock.handlers.get(i);
+ EncodedTypeAddrPair handler = encodedCatchHandler.handlers[i];
+ handler.addr = handlerInsn.location;
+ }
+ tryItemIdx++;
+ }
+ }
+
+ /**
+ * Given a switch instruction, find the associated data's raw[] form, and update
+ * the targets of the switch instruction to point to the correct instructions.
+ */
+ private void readSwitchInstruction(MSwitchInsn switchInsn,
+ Map<Integer,MInsn> insnLocationMap) {
+ // Find the data.
+ ContainsTarget containsTarget = (ContainsTarget) switchInsn.insn.info.format;
+ int dataLocation = switchInsn.location + (int) containsTarget.getTarget(switchInsn.insn);
+ switchInsn.dataTarget = insnLocationMap.get(dataLocation);
+ if (switchInsn.dataTarget == null) {
+ Log.errorAndQuit("Bad offset calculation for data target in switch insn");
+ }
+
+ // Now read the data.
+ Instruction dataInsn = switchInsn.dataTarget.insn;
+
+ int rawPtr = 2;
+
+ int targetsSize = (int) RawInsnHelper.getUnsignedShortFromTwoBytes(dataInsn.rawBytes, rawPtr);
+ rawPtr += 2;
+
+ int[] keys = new int[targetsSize];
+ int[] targets = new int[targetsSize];
+
+ if (dataInsn.rawType == 1) {
+ switchInsn.packed = true;
+ // Dealing with a packed-switch.
+ // Read the first key.
+ keys[0] = (int) RawInsnHelper.getUnsignedIntFromFourBytes(dataInsn.rawBytes, rawPtr);
+ rawPtr += 4;
+ // Calculate the rest of the keys.
+ for (int i = 1; i < targetsSize; i++) {
+ keys[i] = keys[i - 1] + 1;
+ }
+ } else if (dataInsn.rawType == 2) {
+ // Dealing with a sparse-switch.
+ // Read all of the keys.
+ for (int i = 0; i < targetsSize; i++) {
+ keys[i] = (int) RawInsnHelper.getUnsignedIntFromFourBytes(dataInsn.rawBytes,
+ rawPtr);
+ rawPtr += 4;
+ }
+ }
+
+ // Now read the targets.
+ for (int i = 0; i < targetsSize; i++) {
+ targets[i] = (int) RawInsnHelper.getUnsignedIntFromFourBytes(dataInsn.rawBytes,
+ rawPtr);
+ rawPtr += 4;
+ }
+
+ // Store the keys.
+ switchInsn.keys = keys;
+
+ // Convert our targets[] offsets into pointers to MInsns.
+ for (int target : targets) {
+ int targetLocation = switchInsn.location + target;
+ MInsn targetInsn = insnLocationMap.get(targetLocation);
+ switchInsn.targets.add(targetInsn);
+ if (targetInsn == null) {
+ Log.errorAndQuit("Bad offset calculation for target in switch insn");
+ }
+ }
+ }
+
+ /**
+ * Given a mutatable switch instruction, which may have had some of its branch
+ * targets moved, update all the target offsets in the raw[] form of the instruction.
+ */
+ private void updateSwitchInstruction(MSwitchInsn switchInsn, MutatableCode mutatableCode) {
+ // Update the offset to the data instruction
+ MInsn dataTarget = switchInsn.dataTarget;
+ int dataOffset = dataTarget.location - switchInsn.location;
+ ContainsTarget containsTarget = (ContainsTarget) switchInsn.insn.info.format;
+ containsTarget.setTarget(switchInsn.insn, dataOffset);
+
+ int targetsSize = switchInsn.targets.size();
+
+ int[] keys = switchInsn.keys;
+ int[] targets = new int[targetsSize];
+
+ // Calculate the new offsets.
+ int targetIdx = 0;
+ for (MInsn target : switchInsn.targets) {
+ targets[targetIdx] = target.location - switchInsn.location;
+ targetIdx++;
+ }
+
+ // Now write the data back to the raw bytes.
+ Instruction dataInsn = switchInsn.dataTarget.insn;
+
+ int rawPtr = 2;
+
+ // Write out the size.
+ RawInsnHelper.writeUnsignedShortToTwoBytes(dataInsn.rawBytes, rawPtr, targetsSize);
+ rawPtr += 2;
+
+ // Write out the keys.
+ if (switchInsn.packed) {
+ // Only write out one key - the first.
+ RawInsnHelper.writeUnsignedIntToFourBytes(dataInsn.rawBytes, rawPtr, keys[0]);
+ rawPtr += 4;
+ } else {
+ // Write out all the keys.
+ for (int i = 0; i < targetsSize; i++) {
+ RawInsnHelper.writeUnsignedIntToFourBytes(dataInsn.rawBytes, rawPtr, keys[i]);
+ rawPtr += 4;
+ }
+ }
+
+ // Write out all the targets.
+ for (int i = 0; i < targetsSize; i++) {
+ RawInsnHelper.writeUnsignedIntToFourBytes(dataInsn.rawBytes, rawPtr, targets[i]);
+ rawPtr += 4;
+ }
+ }
+
+ /**
+ * After mutation, data instructions may no longer be 4-byte aligned.
+ * If this is the case, insert nops to align them all.
+ * This makes a number of assumptions about data currently:
+ * - data is always at the end of method insns
+ * - all data instructions are stored contiguously
+ */
+ private void alignDataInstructions(MutatableCode mutatableCode) {
+ // Find all the switch data instructions.
+ List<MInsn> dataInsns = new ArrayList<MInsn>();
+
+ // Update raw sizes of the data instructions as well.
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (mInsn instanceof MSwitchInsn) {
+ // Update the raw size of the instruction.
+ MSwitchInsn switchInsn = (MSwitchInsn) mInsn;
+ int targetsSize = switchInsn.targets.size();
+ Instruction dataInsn = switchInsn.dataTarget.insn;
+ if (switchInsn.packed) {
+ dataInsn.rawSize = (targetsSize * 2) + 4;
+ } else {
+ dataInsn.rawSize = (targetsSize * 4) + 2;
+ }
+ dataInsns.add(switchInsn.dataTarget);
+ } else if (mInsn instanceof MInsnWithData) {
+ MInsnWithData insnWithData =
+ (MInsnWithData) mInsn;
+ dataInsns.add(insnWithData.dataTarget);
+ }
+ }
+
+ // Only need to align switch data instructions if there are any!
+ if (!dataInsns.isEmpty()) {
+
+ Log.debug("Found data instructions, checking alignment...");
+
+ // Sort data_insns by location.
+ Collections.sort(dataInsns, new Comparator<MInsn>() {
+ @Override
+ public int compare(MInsn first, MInsn second) {
+ if (first.location < second.location) {
+ return -1;
+ } else if (first.location > second.location) {
+ return 1;
+ }
+ return 0;
+ }
+ });
+
+ boolean performedAlignment = false;
+
+ // Go through all the data insns, and insert an alignment nop if they're unaligned.
+ for (MInsn dataInsn : dataInsns) {
+ if (dataInsn.location % 2 != 0) {
+ Log.debug("Aligning data instruction with a nop.");
+ int alignmentNopIdx = mutatableCode.getInstructionIndex(dataInsn);
+ MInsn nop = new MInsn();
+ nop.insn = new Instruction();
+ nop.insn.info = Instruction.getOpcodeInfo(Opcode.NOP);
+ mutatableCode.insertInstructionAt(nop, alignmentNopIdx);
+ performedAlignment = true;
+ }
+ }
+
+ if (!performedAlignment) {
+ Log.debug("Alignment okay.");
+ }
+ }
+ }
+
+ /**
+ * Determine if a particular instruction is a branch instruction, based on opcode.
+ */
+ private boolean isInstructionBranch(Instruction insn) {
+ Opcode opcode = insn.info.opcode;
+ if (Opcode.isBetween(opcode, Opcode.IF_EQ, Opcode.IF_LEZ)
+ || Opcode.isBetween(opcode, Opcode.GOTO, Opcode.GOTO_32)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Determine if a particular instruction is a switch instruction, based on opcode.
+ */
+ private boolean isInstructionSwitch(Instruction insn) {
+ Opcode opcode = insn.info.opcode;
+ if (Opcode.isBetween(opcode, Opcode.PACKED_SWITCH, Opcode.SPARSE_SWITCH)) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isInstructionFillArrayData(Instruction insn) {
+ return (insn.info.opcode == Opcode.FILL_ARRAY_DATA);
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/IdCreator.java b/tools/dexfuzz/src/dexfuzz/program/IdCreator.java
new file mode 100644
index 0000000..c506fa6
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/IdCreator.java
@@ -0,0 +1,804 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program;
+
+import dexfuzz.Log;
+import dexfuzz.rawdex.FieldIdItem;
+import dexfuzz.rawdex.MethodIdItem;
+import dexfuzz.rawdex.Offset;
+import dexfuzz.rawdex.Offsettable;
+import dexfuzz.rawdex.ProtoIdItem;
+import dexfuzz.rawdex.RawDexFile;
+import dexfuzz.rawdex.RawDexObject.IndexUpdateKind;
+import dexfuzz.rawdex.StringDataItem;
+import dexfuzz.rawdex.StringIdItem;
+import dexfuzz.rawdex.TypeIdItem;
+import dexfuzz.rawdex.TypeItem;
+import dexfuzz.rawdex.TypeList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Responsible for the finding and creation of TypeIds, MethodIds, FieldIds, and StringIds,
+ * during mutation.
+ */
+public class IdCreator {
+ private RawDexFile rawDexFile;
+
+ public IdCreator(RawDexFile rawDexFile) {
+ this.rawDexFile = rawDexFile;
+ }
+
+ private int findProtoIdInsertionPoint(String signature) {
+ int returnTypeIdx = findTypeId(convertSignatureToReturnType(signature));
+ String[] parameterListStrings = convertSignatureToParameterList(signature);
+ TypeList parameterList = null;
+ if (parameterListStrings.length > 0) {
+ parameterList = findTypeList(parameterListStrings);
+ }
+
+ if (returnTypeIdx < 0) {
+ Log.errorAndQuit("Did not create necessary return type before finding insertion "
+ + "point for new proto!");
+ }
+
+ if (parameterListStrings.length > 0 && parameterList == null) {
+ Log.errorAndQuit("Did not create necessary parameter list before finding insertion "
+ + "point for new proto!");
+ }
+
+ int protoIdIdx = 0;
+ for (ProtoIdItem protoId : rawDexFile.protoIds) {
+ if (returnTypeIdx < protoId.returnTypeIdx) {
+ break;
+ }
+ if (returnTypeIdx == protoId.returnTypeIdx
+ && parameterListStrings.length == 0) {
+ break;
+ }
+ if (returnTypeIdx == protoId.returnTypeIdx
+ && parameterListStrings.length > 0
+ && protoId.parametersOff.pointsToSomething()
+ && parameterList.comesBefore(
+ (TypeList) protoId.parametersOff.getPointedToItem())) {
+ break;
+ }
+ protoIdIdx++;
+ }
+ return protoIdIdx;
+ }
+
+ private int findMethodIdInsertionPoint(String className, String methodName, String signature) {
+ int classIdx = findTypeId(className);
+ int nameIdx = findString(methodName);
+ int protoIdx = findProtoId(signature);
+
+ if (classIdx < 0 || nameIdx < 0 || protoIdx < 0) {
+ Log.errorAndQuit("Did not create necessary class, name or proto strings before finding "
+ + " insertion point for new method!");
+ }
+
+ int methodIdIdx = 0;
+ for (MethodIdItem methodId : rawDexFile.methodIds) {
+ if (classIdx < methodId.classIdx) {
+ break;
+ }
+ if (classIdx == methodId.classIdx && nameIdx < methodId.nameIdx) {
+ break;
+ }
+ if (classIdx == methodId.classIdx && nameIdx == methodId.nameIdx
+ && protoIdx < methodId.protoIdx) {
+ break;
+ }
+ methodIdIdx++;
+ }
+ return methodIdIdx;
+ }
+
+ private int findTypeIdInsertionPoint(String className) {
+ int descriptorIdx = findString(className);
+
+ if (descriptorIdx < 0) {
+ Log.errorAndQuit("Did not create necessary descriptor string before finding "
+ + " insertion point for new type!");
+ }
+
+ int typeIdIdx = 0;
+ for (TypeIdItem typeId : rawDexFile.typeIds) {
+ if (descriptorIdx < typeId.descriptorIdx) {
+ break;
+ }
+ typeIdIdx++;
+ }
+ return typeIdIdx;
+ }
+
+ private int findStringDataInsertionPoint(String string) {
+ int stringDataIdx = 0;
+ for (StringDataItem stringData : rawDexFile.stringDatas) {
+ if (stringData.getSize() > 0 && stringData.getString().compareTo(string) >= 0) {
+ break;
+ }
+ stringDataIdx++;
+ }
+ return stringDataIdx;
+ }
+
+ private int findFieldIdInsertionPoint(String className, String typeName, String fieldName) {
+ int classIdx = findTypeId(className);
+ int typeIdx = findTypeId(typeName);
+ int nameIdx = findString(fieldName);
+
+ if (classIdx < 0 || typeIdx < 0 || nameIdx < 0) {
+ Log.errorAndQuit("Did not create necessary class, type or name strings before finding "
+ + " insertion point for new field!");
+ }
+
+ int fieldIdIdx = 0;
+ for (FieldIdItem fieldId : rawDexFile.fieldIds) {
+ if (classIdx < fieldId.classIdx) {
+ break;
+ }
+ if (classIdx == fieldId.classIdx && nameIdx < fieldId.nameIdx) {
+ break;
+ }
+ if (classIdx == fieldId.classIdx && nameIdx == fieldId.nameIdx
+ && typeIdx < fieldId.typeIdx) {
+ break;
+ }
+ fieldIdIdx++;
+ }
+ return fieldIdIdx;
+ }
+
+ private int createMethodId(String className, String methodName, String signature) {
+ if (rawDexFile.methodIds.size() >= 65536) {
+ Log.errorAndQuit("Referenced too many methods for the DEX file.");
+ }
+
+ // Search for (or create) the prototype.
+ int protoIdx = findOrCreateProtoId(signature);
+
+ // Search for (or create) the owning class.
+ // NB: findOrCreateProtoId could create new types, so this must come
+ // after it!
+ int typeIdIdx = findOrCreateTypeId(className);
+
+ // Search for (or create) the string representing the method name.
+ // NB: findOrCreateProtoId/TypeId could create new strings, so this must come
+ // after them!
+ int methodNameStringIdx = findOrCreateString(methodName);
+
+ // Create MethodIdItem.
+ MethodIdItem newMethodId = new MethodIdItem();
+ newMethodId.classIdx = (short) typeIdIdx;
+ newMethodId.protoIdx = (short) protoIdx;
+ newMethodId.nameIdx = methodNameStringIdx;
+
+ // MethodIds must be ordered.
+ int newMethodIdIdx = findMethodIdInsertionPoint(className, methodName, signature);
+
+ rawDexFile.methodIds.add(newMethodIdIdx, newMethodId);
+
+ // Insert into OffsetTracker.
+ if (newMethodIdIdx == 0) {
+ rawDexFile.getOffsetTracker()
+ .insertNewOffsettableAsFirstOfType(newMethodId, rawDexFile);
+ } else {
+ MethodIdItem prevMethodId = rawDexFile.methodIds.get(newMethodIdIdx - 1);
+ rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newMethodId, prevMethodId);
+ }
+
+ Log.info(String.format("Created new MethodIdItem for %s %s %s, index: 0x%04x",
+ className, methodName, signature, newMethodIdIdx));
+
+ // Now that we've potentially moved a lot of method IDs along, all references
+ // to them need to be updated.
+ rawDexFile.incrementIndex(IndexUpdateKind.METHOD_ID, newMethodIdIdx);
+
+ // All done, return the index for the new method.
+ return newMethodIdIdx;
+ }
+
+ private int findMethodId(String className, String methodName, String signature) {
+ int classIdx = findTypeId(className);
+ if (classIdx == -1) {
+ return -1;
+ }
+ int nameIdx = findString(methodName);
+ if (nameIdx == -1) {
+ return -1;
+ }
+ int protoIdx = findProtoId(signature);
+ if (nameIdx == -1) {
+ return -1;
+ }
+
+ int methodIdIdx = 0;
+ for (MethodIdItem methodId : rawDexFile.methodIds) {
+ if (classIdx == methodId.classIdx
+ && nameIdx == methodId.nameIdx
+ && protoIdx == methodId.protoIdx) {
+ return methodIdIdx;
+ }
+ methodIdIdx++;
+ }
+ return -1;
+ }
+
+ /**
+ * Given a fully qualified class name (Ljava/lang/System;), method name (gc) and
+ * and signature (()V), either find the MethodId in our DEX file's table, or create it.
+ */
+ public int findOrCreateMethodId(String className, String methodName, String shorty) {
+ int methodIdIdx = findMethodId(className, methodName, shorty);
+ if (methodIdIdx != -1) {
+ return methodIdIdx;
+ }
+ return createMethodId(className, methodName, shorty);
+ }
+
+ private int createTypeId(String className) {
+ if (rawDexFile.typeIds.size() >= 65536) {
+ Log.errorAndQuit("Referenced too many classes for the DEX file.");
+ }
+
+ // Search for (or create) the string representing the class descriptor.
+ int descriptorStringIdx = findOrCreateString(className);
+
+ // Create TypeIdItem.
+ TypeIdItem newTypeId = new TypeIdItem();
+ newTypeId.descriptorIdx = descriptorStringIdx;
+
+ // TypeIds must be ordered.
+ int newTypeIdIdx = findTypeIdInsertionPoint(className);
+
+ rawDexFile.typeIds.add(newTypeIdIdx, newTypeId);
+
+ // Insert into OffsetTracker.
+ if (newTypeIdIdx == 0) {
+ rawDexFile.getOffsetTracker().insertNewOffsettableAsFirstOfType(newTypeId, rawDexFile);
+ } else {
+ TypeIdItem prevTypeId = rawDexFile.typeIds.get(newTypeIdIdx - 1);
+ rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newTypeId, prevTypeId);
+ }
+
+ Log.info(String.format("Created new ClassIdItem for %s, index: 0x%04x",
+ className, newTypeIdIdx));
+
+ // Now that we've potentially moved a lot of type IDs along, all references
+ // to them need to be updated.
+ rawDexFile.incrementIndex(IndexUpdateKind.TYPE_ID, newTypeIdIdx);
+
+ // All done, return the index for the new class.
+ return newTypeIdIdx;
+ }
+
+ private int findTypeId(String className) {
+ int descriptorIdx = findString(className);
+ if (descriptorIdx == -1) {
+ return -1;
+ }
+
+ // Search for class.
+ int typeIdIdx = 0;
+ for (TypeIdItem typeId : rawDexFile.typeIds) {
+ if (descriptorIdx == typeId.descriptorIdx) {
+ return typeIdIdx;
+ }
+ typeIdIdx++;
+ }
+ return -1;
+ }
+
+ /**
+ * Given a fully qualified class name (Ljava/lang/System;)
+ * either find the TypeId in our DEX file's table, or create it.
+ */
+ public int findOrCreateTypeId(String className) {
+ int typeIdIdx = findTypeId(className);
+ if (typeIdIdx != -1) {
+ return typeIdIdx;
+ }
+ return createTypeId(className);
+ }
+
+ private int createString(String string) {
+ // Didn't find it, create one...
+ int stringsCount = rawDexFile.stringIds.size();
+ if (stringsCount != rawDexFile.stringDatas.size()) {
+ Log.errorAndQuit("Corrupted DEX file, len(StringIDs) != len(StringDatas)");
+ }
+
+ // StringData must be ordered.
+ int newStringIdx = findStringDataInsertionPoint(string);
+
+ // Create StringDataItem.
+ StringDataItem newStringData = new StringDataItem();
+ newStringData.setSize(string.length());
+ newStringData.setString(string);
+
+ rawDexFile.stringDatas.add(newStringIdx, newStringData);
+
+ // Insert into OffsetTracker.
+ // (Need to save the Offsettable, because the StringIdItem will point to it.)
+ Offsettable offsettableStringData = null;
+ if (newStringIdx == 0) {
+ offsettableStringData =
+ rawDexFile.getOffsetTracker()
+ .insertNewOffsettableAsFirstOfType(newStringData, rawDexFile);
+ } else {
+ StringDataItem prevStringData = rawDexFile.stringDatas.get(newStringIdx - 1);
+ offsettableStringData = rawDexFile.getOffsetTracker()
+ .insertNewOffsettableAfter(newStringData, prevStringData);
+ }
+
+ // Create StringIdItem.
+ StringIdItem newStringId = new StringIdItem();
+ newStringId.stringDataOff = new Offset(false);
+ newStringId.stringDataOff.pointToNew(offsettableStringData);
+
+ rawDexFile.stringIds.add(newStringIdx, newStringId);
+
+ // Insert into OffsetTracker.
+ if (newStringIdx == 0) {
+ rawDexFile.getOffsetTracker()
+ .insertNewOffsettableAsFirstOfType(newStringId, rawDexFile);
+ } else {
+ StringIdItem prevStringId = rawDexFile.stringIds.get(newStringIdx - 1);
+ rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newStringId, prevStringId);
+ }
+
+
+ Log.info(String.format("Created new StringIdItem and StringDataItem for %s, index: 0x%04x",
+ string, newStringIdx));
+
+ // Now that we've potentially moved a lot of string IDs along, all references
+ // to them need to be updated.
+ rawDexFile.incrementIndex(IndexUpdateKind.STRING_ID, newStringIdx);
+
+ // All done, return the index for the new string.
+ return newStringIdx;
+ }
+
+ private int findString(String string) {
+ // Search for string.
+ int stringIdx = 0;
+ for (StringDataItem stringDataItem : rawDexFile.stringDatas) {
+ if (stringDataItem.getSize() == 0 && string.isEmpty()) {
+ return stringIdx;
+ } else if (stringDataItem.getSize() > 0 && stringDataItem.getString().equals(string)) {
+ return stringIdx;
+ }
+ stringIdx++;
+ }
+ return -1;
+ }
+
+ /**
+ * Given a string, either find the StringId in our DEX file's table, or create it.
+ */
+ public int findOrCreateString(String string) {
+ int stringIdx = findString(string);
+ if (stringIdx != -1) {
+ return stringIdx;
+ }
+ return createString(string);
+ }
+
+ private int createFieldId(String className, String typeName, String fieldName) {
+ if (rawDexFile.fieldIds.size() >= 65536) {
+ Log.errorAndQuit("Referenced too many fields for the DEX file.");
+ }
+
+ // Search for (or create) the owning class.
+ int classIdx = findOrCreateTypeId(className);
+
+ // Search for (or create) the field's type.
+ int typeIdx = findOrCreateTypeId(typeName);
+
+ // The creation of the typeIdx may have changed the classIdx, search again!
+ classIdx = findOrCreateTypeId(className);
+
+ // Search for (or create) the string representing the field name.
+ int fieldNameStringIdx = findOrCreateString(fieldName);
+
+ // Create FieldIdItem.
+ FieldIdItem newFieldId = new FieldIdItem();
+ newFieldId.classIdx = (short) classIdx;
+ newFieldId.typeIdx = (short) typeIdx;
+ newFieldId.nameIdx = fieldNameStringIdx;
+
+ // FieldIds must be ordered.
+ int newFieldIdIdx = findFieldIdInsertionPoint(className, typeName, fieldName);
+
+ rawDexFile.fieldIds.add(newFieldIdIdx, newFieldId);
+
+ // Insert into OffsetTracker.
+ if (newFieldIdIdx == 0 && rawDexFile.fieldIds.size() == 1) {
+ // Special case: we didn't have any fields before!
+ rawDexFile.getOffsetTracker()
+ .insertNewOffsettableAsFirstEverField(newFieldId, rawDexFile);
+ } else if (newFieldIdIdx == 0) {
+ rawDexFile.getOffsetTracker().insertNewOffsettableAsFirstOfType(newFieldId, rawDexFile);
+ } else {
+ FieldIdItem prevFieldId = rawDexFile.fieldIds.get(newFieldIdIdx - 1);
+ rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newFieldId, prevFieldId);
+ }
+
+ Log.info(String.format("Created new FieldIdItem for %s %s %s, index: 0x%04x",
+ className, typeName, fieldName, newFieldIdIdx));
+
+ // Now that we've potentially moved a lot of field IDs along, all references
+ // to them need to be updated.
+ rawDexFile.incrementIndex(IndexUpdateKind.FIELD_ID, newFieldIdIdx);
+
+ // All done, return the index for the new field.
+ return newFieldIdIdx;
+ }
+
+ private int findFieldId(String className, String typeName, String fieldName) {
+ int classIdx = findTypeId(className);
+ if (classIdx == -1) {
+ return -1;
+ }
+ int typeIdx = findTypeId(typeName);
+ if (typeIdx == -1) {
+ return -1;
+ }
+ int nameIdx = findString(fieldName);
+ if (nameIdx == -1) {
+ return -1;
+ }
+
+ int fieldIdIdx = 0;
+ for (FieldIdItem fieldId : rawDexFile.fieldIds) {
+ if (classIdx == fieldId.classIdx
+ && typeIdx == fieldId.typeIdx
+ && nameIdx == fieldId.nameIdx) {
+ return fieldIdIdx;
+ }
+ fieldIdIdx++;
+ }
+ return -1;
+ }
+
+ /**
+ * Given a field's fully qualified class name, type name, and name,
+ * either find the FieldId in our DEX file's table, or create it.
+ */
+ public int findOrCreateFieldId(String className, String typeName, String fieldName) {
+ int fieldIdx = findFieldId(className, typeName, fieldName);
+ if (fieldIdx != -1) {
+ return fieldIdx;
+ }
+ return createFieldId(className, typeName, fieldName);
+ }
+
+ /**
+ * Returns a 1 or 2 element String[]. If 1 element, the only element is the return type
+ * part of the signature. If 2 elements, the first is the parameters, the second is
+ * the return type.
+ */
+ private String[] convertSignatureToParametersAndReturnType(String signature) {
+ if (signature.charAt(0) != '(' || !signature.contains(")")) {
+ Log.errorAndQuit("Invalid signature: " + signature);
+ }
+ String[] elems = signature.substring(1).split("\\)");
+ return elems;
+ }
+
+ private String[] convertSignatureToParameterList(String signature) {
+ String[] elems = convertSignatureToParametersAndReturnType(signature);
+ String parameters = "";
+ if (elems.length == 2) {
+ parameters = elems[0];
+ }
+
+ List<String> parameterList = new ArrayList<String>();
+
+ int typePointer = 0;
+ while (typePointer != parameters.length()) {
+ if (elems[0].charAt(typePointer) == 'L') {
+ int start = typePointer;
+ // Read up to the next ;
+ while (elems[0].charAt(typePointer) != ';') {
+ typePointer++;
+ }
+ parameterList.add(parameters.substring(start, typePointer + 1));
+ } else {
+ parameterList.add(Character.toString(parameters.charAt(typePointer)));
+ }
+ typePointer++;
+ }
+
+ return parameterList.toArray(new String[]{});
+ }
+
+ private String convertSignatureToReturnType(String signature) {
+ String[] elems = convertSignatureToParametersAndReturnType(signature);
+ String returnType = "";
+ if (elems.length == 1) {
+ returnType = elems[0];
+ } else {
+ returnType = elems[1];
+ }
+
+ return returnType;
+ }
+
+ private String convertSignatureToShorty(String signature) {
+ String[] elems = convertSignatureToParametersAndReturnType(signature);
+
+ StringBuilder shortyBuilder = new StringBuilder();
+
+ String parameters = "";
+ String returnType = "";
+
+ if (elems.length == 1) {
+ shortyBuilder.append("V");
+ } else {
+ parameters = elems[0];
+ returnType = elems[1];
+ char returnChar = returnType.charAt(0);
+ // Arrays are references in shorties.
+ if (returnChar == '[') {
+ returnChar = 'L';
+ }
+ shortyBuilder.append(returnChar);
+ }
+
+ int typePointer = 0;
+ while (typePointer != parameters.length()) {
+ if (parameters.charAt(typePointer) == 'L') {
+ shortyBuilder.append('L');
+ // Read up to the next ;
+ while (parameters.charAt(typePointer) != ';') {
+ typePointer++;
+ if (typePointer == parameters.length()) {
+ Log.errorAndQuit("Illegal type specified in signature - L with no ;!");
+ }
+ }
+ } else if (parameters.charAt(typePointer) == '[') {
+ // Arrays are references in shorties.
+ shortyBuilder.append('L');
+ // Read past all the [s
+ while (parameters.charAt(typePointer) == '[') {
+ typePointer++;
+ }
+ if (parameters.charAt(typePointer) == 'L') {
+ // Read up to the next ;
+ while (parameters.charAt(typePointer) != ';') {
+ typePointer++;
+ if (typePointer == parameters.length()) {
+ Log.errorAndQuit("Illegal type specified in signature - L with no ;!");
+ }
+ }
+ }
+ } else {
+ shortyBuilder.append(parameters.charAt(typePointer));
+ }
+
+ typePointer++;
+ }
+
+ return shortyBuilder.toString();
+ }
+
+ private Integer[] convertParameterListToTypeIdList(String[] parameterList) {
+ List<Integer> typeIdList = new ArrayList<Integer>();
+ for (String parameter : parameterList) {
+ int typeIdx = findTypeId(parameter);
+ if (typeIdx == -1) {
+ return null;
+ }
+ typeIdList.add(typeIdx);
+ }
+ return typeIdList.toArray(new Integer[]{});
+ }
+
+ private TypeList createTypeList(String[] parameterList) {
+ TypeList typeList = new TypeList();
+ List<TypeItem> typeItemList = new ArrayList<TypeItem>();
+
+ // This must be done as two passes, one to create all the types,
+ // and then one to put them in the type list.
+ for (String parameter : parameterList) {
+ findOrCreateTypeId(parameter);
+ }
+
+ // Now actually put them in the list.
+ for (String parameter : parameterList) {
+ TypeItem typeItem = new TypeItem();
+ typeItem.typeIdx = (short) findOrCreateTypeId(parameter);
+ typeItemList.add(typeItem);
+ }
+ typeList.list = typeItemList.toArray(new TypeItem[]{});
+ typeList.size = typeItemList.size();
+
+ // Insert into OffsetTracker.
+ if (rawDexFile.typeLists == null) {
+ // Special case: we didn't have any fields before!
+ Log.info("Need to create first type list.");
+ rawDexFile.typeLists = new ArrayList<TypeList>();
+ rawDexFile.getOffsetTracker()
+ .insertNewOffsettableAsFirstEverTypeList(typeList, rawDexFile);
+ } else {
+ TypeList prevTypeList =
+ rawDexFile.typeLists.get(rawDexFile.typeLists.size() - 1);
+ rawDexFile.getOffsetTracker().insertNewOffsettableAfter(typeList, prevTypeList);
+ }
+
+ // Finally, add this new TypeList to the list of them.
+ rawDexFile.typeLists.add(typeList);
+
+ return typeList;
+ }
+
+ private TypeList findTypeList(String[] parameterList) {
+ Integer[] typeIdList = convertParameterListToTypeIdList(parameterList);
+ if (typeIdList == null) {
+ return null;
+ }
+
+ if (rawDexFile.typeLists == null) {
+ // There's no type lists yet!
+ return null;
+ }
+
+ for (TypeList typeList : rawDexFile.typeLists) {
+ if (typeList.size != typeIdList.length) {
+ continue;
+ }
+
+ boolean found = true;
+ int idx = 0;
+ for (TypeItem typeItem : typeList.list) {
+ if (typeItem.typeIdx != typeIdList[idx]) {
+ found = false;
+ break;
+ }
+ idx++;
+ }
+ if (found && idx == parameterList.length) {
+ return typeList;
+ }
+ }
+
+ return null;
+ }
+
+ private TypeList findOrCreateTypeList(String[] parameterList) {
+ TypeList typeList = findTypeList(parameterList);
+ if (typeList != null) {
+ return typeList;
+ }
+ return createTypeList(parameterList);
+ }
+
+ private int createProtoId(String signature) {
+ String shorty = convertSignatureToShorty(signature);
+ String returnType = convertSignatureToReturnType(signature);
+ String[] parameterList = convertSignatureToParameterList(signature);
+
+ if (rawDexFile.protoIds.size() >= 65536) {
+ Log.errorAndQuit("Referenced too many protos for the DEX file.");
+ }
+
+ TypeList typeList = null;
+ Offsettable typeListOffsettable = null;
+
+ if (parameterList.length > 0) {
+ // Search for (or create) the parameter list.
+ typeList = findOrCreateTypeList(parameterList);
+
+ typeListOffsettable =
+ rawDexFile.getOffsetTracker().getOffsettableForItem(typeList);
+ }
+
+ // Search for (or create) the return type.
+ int returnTypeIdx = findOrCreateTypeId(returnType);
+
+ // Search for (or create) the shorty string.
+ int shortyIdx = findOrCreateString(shorty);
+
+ // Create ProtoIdItem.
+ ProtoIdItem newProtoId = new ProtoIdItem();
+ newProtoId.shortyIdx = shortyIdx;
+ newProtoId.returnTypeIdx = returnTypeIdx;
+ newProtoId.parametersOff = new Offset(false);
+ if (parameterList.length > 0) {
+ newProtoId.parametersOff.pointToNew(typeListOffsettable);
+ }
+
+ // ProtoIds must be ordered.
+ int newProtoIdIdx = findProtoIdInsertionPoint(signature);
+
+ rawDexFile.protoIds.add(newProtoIdIdx, newProtoId);
+
+ // Insert into OffsetTracker.
+ if (newProtoIdIdx == 0) {
+ rawDexFile.getOffsetTracker().insertNewOffsettableAsFirstOfType(newProtoId, rawDexFile);
+ } else {
+ ProtoIdItem prevProtoId = rawDexFile.protoIds.get(newProtoIdIdx - 1);
+ rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newProtoId, prevProtoId);
+ }
+
+ Log.info(String.format("Created new ProtoIdItem for %s, index: 0x%04x",
+ signature, newProtoIdIdx));
+
+ // Now that we've potentially moved a lot of proto IDs along, all references
+ // to them need to be updated.
+ rawDexFile.incrementIndex(IndexUpdateKind.PROTO_ID, newProtoIdIdx);
+
+ // All done, return the index for the new proto.
+ return newProtoIdIdx;
+ }
+
+ private int findProtoId(String signature) {
+ String shorty = convertSignatureToShorty(signature);
+ String returnType = convertSignatureToReturnType(signature);
+ String[] parameterList = convertSignatureToParameterList(signature);
+
+ int shortyIdx = findString(shorty);
+ if (shortyIdx == -1) {
+ return -1;
+ }
+ int returnTypeIdx = findTypeId(returnType);
+ if (returnTypeIdx == -1) {
+ return -1;
+ }
+
+ // Only look for a TypeList if there's a parameter list.
+ TypeList typeList = null;
+ if (parameterList.length > 0) {
+ typeList = findTypeList(parameterList);
+ if (typeList == null) {
+ return -1;
+ }
+ }
+
+ int protoIdIdx = 0;
+ for (ProtoIdItem protoId : rawDexFile.protoIds) {
+ if (parameterList.length > 0) {
+ // With parameters.
+ if (shortyIdx == protoId.shortyIdx
+ && returnTypeIdx == protoId.returnTypeIdx
+ && typeList.equals(protoId.parametersOff.getPointedToItem())) {
+ return protoIdIdx;
+ }
+ } else {
+ // Without parameters.
+ if (shortyIdx == protoId.shortyIdx
+ && returnTypeIdx == protoId.returnTypeIdx) {
+ return protoIdIdx;
+ }
+ }
+ protoIdIdx++;
+ }
+ return -1;
+ }
+
+ private int findOrCreateProtoId(String signature) {
+ int protoIdx = findProtoId(signature);
+ if (protoIdx != -1) {
+ return protoIdx;
+ }
+ return createProtoId(signature);
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/MBranchInsn.java b/tools/dexfuzz/src/dexfuzz/program/MBranchInsn.java
new file mode 100644
index 0000000..ea66844
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/MBranchInsn.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program;
+
+/**
+ * A subclass of the MInsn, that tracks its target instruction.
+ */
+public class MBranchInsn extends MInsn {
+ /**
+ * The MInsn this branch instruction branches to.
+ */
+ public MInsn target;
+
+ /**
+ * Clone this MBranchInsn, and clone the wrapped Instruction.
+ */
+ public MBranchInsn clone() {
+ MBranchInsn newInsn = new MBranchInsn();
+ newInsn.insn = insn.clone();
+ newInsn.target = target;
+ return newInsn;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/MInsn.java b/tools/dexfuzz/src/dexfuzz/program/MInsn.java
new file mode 100644
index 0000000..10f7755
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/MInsn.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program;
+
+import dexfuzz.rawdex.Instruction;
+
+/**
+ * Base class that is a thin wrapper for Instructions currently, also tracking location
+ * as the instruction is moved around.
+ */
+public class MInsn {
+ /**
+ * The raw DEX instruction that this instruction represents.
+ */
+ public Instruction insn;
+
+
+ /**
+ * The location of this instruction, as an offset in code words from the beginning.
+ * May become invalid if instructions around it are mutated.
+ */
+ public int location;
+
+ /**
+ * Denotes if the currently associated location can be trusted.
+ */
+ public boolean locationUpdated;
+
+ /**
+ * Clone this MInsn, and clone the wrapped Instruction.
+ */
+ public MInsn clone() {
+ MInsn newInsn = new MInsn();
+ newInsn.insn = insn.clone();
+ // It is the responsibility of the cloner to update these values.
+ newInsn.location = location;
+ newInsn.locationUpdated = locationUpdated;
+ return newInsn;
+ }
+
+ /**
+ * Get the String representation of an instruction.
+ */
+ public String toString() {
+ return String.format("{0x%04x%s: %s}",
+ location,
+ (locationUpdated) ? "!" : "",
+ insn.toString());
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/MInsnWithData.java b/tools/dexfuzz/src/dexfuzz/program/MInsnWithData.java
new file mode 100644
index 0000000..ffed883
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/MInsnWithData.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program;
+
+/**
+ * A subclass of the MInsn, that tracks the data instruction.
+ */
+public class MInsnWithData extends MInsn {
+ /**
+ * The MInsn that represents the data this instruction uses.
+ */
+ public MInsn dataTarget;
+
+ /**
+ * Clone this MInsnWithData, and clone the wrapped Instruction.
+ */
+ public MInsnWithData clone() {
+ MInsnWithData newInsn = new MInsnWithData();
+ newInsn.insn = insn.clone();
+ newInsn.dataTarget = dataTarget;
+ return newInsn;
+ }
+} \ No newline at end of file
diff --git a/tools/dexfuzz/src/dexfuzz/program/MSwitchInsn.java b/tools/dexfuzz/src/dexfuzz/program/MSwitchInsn.java
new file mode 100644
index 0000000..d8693fe
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/MSwitchInsn.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A subclass of the MInsnWithData, that also has multiple jump targets.
+ */
+public class MSwitchInsn extends MInsnWithData {
+ /**
+ * The MInsns this switch instruction branches to.
+ */
+ public List<MInsn> targets = new LinkedList<MInsn>();
+
+ public boolean packed;
+
+ public int[] keys;
+
+ /**
+ * Clone this MSwitchInsn, and clone the wrapped Instruction.
+ */
+ public MSwitchInsn clone() {
+ MSwitchInsn newInsn = new MSwitchInsn();
+ newInsn.insn = insn.clone();
+ newInsn.dataTarget = dataTarget;
+ newInsn.packed = packed;
+ for (MInsn target : targets) {
+ newInsn.targets.add(target);
+ }
+ newInsn.keys = new int[keys.length];
+ System.arraycopy(keys, 0, newInsn.keys, 0, keys.length);
+ return newInsn;
+ }
+} \ No newline at end of file
diff --git a/tools/dexfuzz/src/dexfuzz/program/MTryBlock.java b/tools/dexfuzz/src/dexfuzz/program/MTryBlock.java
new file mode 100644
index 0000000..a1dc029
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/MTryBlock.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program;
+
+import java.util.List;
+
+/**
+ * Tracks where try blocks start and end.
+ */
+public class MTryBlock {
+ public MInsn startInsn;
+ public MInsn endInsn;
+ public List<MInsn> handlers;
+ public MInsn catchAllHandler;
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/MutatableCode.java b/tools/dexfuzz/src/dexfuzz/program/MutatableCode.java
new file mode 100644
index 0000000..c56b1bc
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/MutatableCode.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program;
+
+import dexfuzz.Log;
+import dexfuzz.rawdex.Instruction;
+import dexfuzz.rawdex.Opcode;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A class that represents a CodeItem in a way that is more amenable to mutation.
+ */
+public class MutatableCode {
+ /**
+ * To ensure we update the correct CodeItem in the raw DEX file.
+ */
+ public int codeItemIdx;
+
+ /**
+ * This is an index into the Program's list of MutatableCodes.
+ */
+ public int mutatableCodeIdx;
+
+ /**
+ * Number of registers this code uses.
+ */
+ public short registersSize;
+
+ /**
+ * Number of ins this code has.
+ */
+ public short insSize;
+
+ /**
+ * Number of outs this code has.
+ */
+ public short outsSize;
+
+ /**
+ * Number of tries this code has.
+ */
+ public short triesSize;
+
+ /**
+ * CodeTranslator is responsible for creating this, and
+ * converting it back to a list of Instructions.
+ */
+ private List<MInsn> mutatableInsns;
+
+ /**
+ * CodeTranslator is responsible for creating this, and
+ * converting it back to the correct form for CodeItems.
+ */
+ public List<MTryBlock> mutatableTries;
+
+ /**
+ * The name of the method this code represents.
+ */
+ public String name;
+ public String shorty;
+ public boolean isStatic;
+
+ /**
+ * The Program that owns this MutatableCode.
+ * Currently used to get the size of constant pools for
+ * PoolIndexChanger/RandomInstructionGenerator
+ */
+ public Program program;
+
+ private short originalInVReg;
+ private short tempVRegsAllocated;
+ private short initialTempVReg;
+ private boolean vregsNeedCopying;
+ private int numMoveInsnsGenerated;
+
+ public MutatableCode(Program program) {
+ this.program = program;
+ this.mutatableInsns = new LinkedList<MInsn>();
+ }
+
+ /**
+ * Call this to update all instructions after the provided mInsn, to have their
+ * locations adjusted by the provided offset. It will also mark that they have been updated.
+ */
+ public void updateInstructionLocationsAfter(MInsn mInsn, int offset) {
+ boolean updating = false;
+ for (MInsn mInsnChecking : mutatableInsns) {
+ if (updating) {
+ mInsnChecking.locationUpdated = true;
+ mInsnChecking.location += offset;
+ } else {
+ if (mInsnChecking == mInsn) {
+ updating = true;
+ }
+ }
+
+ }
+ }
+
+ private void recalculateLocations() {
+ int loc = 0;
+ for (MInsn mInsn : mutatableInsns) {
+ mInsn.location = loc;
+ loc += mInsn.insn.getSize();
+ }
+ }
+
+ public List<MInsn> getInstructions() {
+ return Collections.unmodifiableList(mutatableInsns);
+ }
+
+ public int getInstructionCount() {
+ return mutatableInsns.size();
+ }
+
+ public int getInstructionIndex(MInsn mInsn) {
+ return mutatableInsns.indexOf(mInsn);
+ }
+
+ public MInsn getInstructionAt(int idx) {
+ return mutatableInsns.get(idx);
+ }
+
+ public void addInstructionToEnd(MInsn mInsn) {
+ mutatableInsns.add(mInsn);
+ }
+
+ public void insertInstructionAfter(MInsn toBeInserted, int insertionIdx) {
+ if ((insertionIdx + 1) < mutatableInsns.size()) {
+ insertInstructionAt(toBeInserted, insertionIdx + 1);
+ } else {
+ // Appending to end.
+ MInsn finalInsn = mutatableInsns.get(mutatableInsns.size() - 1);
+ toBeInserted.location = finalInsn.location + finalInsn.insn.getSize();
+ mutatableInsns.add(toBeInserted);
+ }
+ }
+
+ /**
+ * Has same semantics as List.add()
+ */
+ public void insertInstructionAt(MInsn toBeInserted, int insertionIdx) {
+ MInsn currentInsn = mutatableInsns.get(insertionIdx);
+ toBeInserted.location = currentInsn.location;
+ mutatableInsns.add(insertionIdx , toBeInserted);
+ updateInstructionLocationsAfter(toBeInserted, toBeInserted.insn.getSize());
+ }
+
+ /**
+ * Checks if any MTryBlock's instruction refs pointed at the 'before' MInsn,
+ * and points them to the 'after' MInsn, if so. 'twoWay' will check if 'after'
+ * was pointed to, and point refs to the 'before' insn.
+ * (one-way is used when deleting instructions,
+ * two-way is used when swapping instructions.)
+ */
+ private void updateTryBlocksWithReplacementInsn(MInsn before, MInsn after,
+ boolean twoWay) {
+ if (triesSize > 0) {
+ for (MTryBlock mTryBlock : mutatableTries) {
+ if (mTryBlock.startInsn == before) {
+ Log.debug("Try block's first instruction was updated");
+ mTryBlock.startInsn = after;
+ } else if (twoWay && mTryBlock.startInsn == after) {
+ Log.debug("Try block's first instruction was updated");
+ mTryBlock.startInsn = before;
+ }
+ if (mTryBlock.endInsn == before) {
+ Log.debug("Try block's last instruction was updated");
+ mTryBlock.endInsn = after;
+ } else if (twoWay && mTryBlock.endInsn == after) {
+ Log.debug("Try block's last instruction was updated");
+ mTryBlock.endInsn = before;
+ }
+ if (mTryBlock.catchAllHandler == before) {
+ Log.debug("Try block's catch-all instruction was updated");
+ mTryBlock.catchAllHandler = after;
+ } else if (twoWay && mTryBlock.catchAllHandler == after) {
+ Log.debug("Try block's catch-all instruction was updated");
+ mTryBlock.catchAllHandler = before;
+ }
+
+ List<Integer> matchesIndicesToChange = new ArrayList<Integer>();
+ List<Integer> replacementIndicesToChange = null;
+ if (twoWay) {
+ replacementIndicesToChange = new ArrayList<Integer>();
+ }
+
+ int idx = 0;
+ for (MInsn handler : mTryBlock.handlers) {
+ if (handler == before) {
+ matchesIndicesToChange.add(idx);
+ Log.debug("Try block's handler instruction was updated");
+ } else if (twoWay && handler == after) {
+ replacementIndicesToChange.add(idx);
+ Log.debug("Try block's handler instruction was updated");
+ }
+ idx++;
+ }
+
+ for (int idxToChange : matchesIndicesToChange) {
+ mTryBlock.handlers.set(idxToChange, after);
+ }
+
+ if (twoWay) {
+ for (int idxToChange : replacementIndicesToChange) {
+ mTryBlock.handlers.set(idxToChange, before);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * The actual implementation of deleteInstruction called by
+ * the single-argument deleteInstructions.
+ */
+ private void deleteInstructionFull(MInsn toBeDeleted, int toBeDeletedIdx) {
+ // Make sure we update all locations afterwards first.
+ updateInstructionLocationsAfter(toBeDeleted, -(toBeDeleted.insn.getSize()));
+
+ // Remove it.
+ mutatableInsns.remove(toBeDeletedIdx);
+
+ // Update any branch instructions that branched to the instruction we just deleted!
+
+ // First, pick the replacement target.
+ int replacementTargetIdx = toBeDeletedIdx;
+ if (replacementTargetIdx == mutatableInsns.size()) {
+ replacementTargetIdx--;
+ }
+ MInsn replacementTarget = mutatableInsns.get(replacementTargetIdx);
+
+ for (MInsn mInsn : mutatableInsns) {
+ if (mInsn instanceof MBranchInsn) {
+ // Check if this branch insn points at the insn we just deleted.
+ MBranchInsn branchInsn = (MBranchInsn) mInsn;
+ MInsn target = branchInsn.target;
+ if (target == toBeDeleted) {
+ Log.debug(branchInsn + " was pointing at the deleted instruction, updated.");
+ branchInsn.target = replacementTarget;
+ }
+ } else if (mInsn instanceof MSwitchInsn) {
+ // Check if any of this switch insn's targets points at the insn we just deleted.
+ MSwitchInsn switchInsn = (MSwitchInsn) mInsn;
+ List<Integer> indicesToChange = new ArrayList<Integer>();
+ int idx = 0;
+ for (MInsn target : switchInsn.targets) {
+ if (target == toBeDeleted) {
+ indicesToChange.add(idx);
+ Log.debug(switchInsn + "[" + idx
+ + "] was pointing at the deleted instruction, updated.");
+ }
+ idx++;
+ }
+ for (int idxToChange : indicesToChange) {
+ switchInsn.targets.remove(idxToChange);
+ switchInsn.targets.add(idxToChange, replacementTarget);
+ }
+ }
+ }
+
+ // Now update the try blocks.
+ updateTryBlocksWithReplacementInsn(toBeDeleted, replacementTarget, false);
+ }
+
+ /**
+ * Delete the provided MInsn.
+ */
+ public void deleteInstruction(MInsn toBeDeleted) {
+ deleteInstructionFull(toBeDeleted, mutatableInsns.indexOf(toBeDeleted));
+ }
+
+ /**
+ * Delete the MInsn at the provided index.
+ */
+ public void deleteInstruction(int toBeDeletedIdx) {
+ deleteInstructionFull(mutatableInsns.get(toBeDeletedIdx), toBeDeletedIdx);
+ }
+
+ public void swapInstructionsByIndex(int aIdx, int bIdx) {
+ MInsn aInsn = mutatableInsns.get(aIdx);
+ MInsn bInsn = mutatableInsns.get(bIdx);
+
+ mutatableInsns.set(aIdx, bInsn);
+ mutatableInsns.set(bIdx, aInsn);
+
+ updateTryBlocksWithReplacementInsn(aInsn, bInsn, true);
+
+ recalculateLocations();
+ }
+
+ /**
+ * Some mutators may require the use of temporary registers. For instance,
+ * to easily add in printing of values without having to look for registers
+ * that aren't currently live.
+ * The idea is to allocate these registers at the top of the set of registers.
+ * Because this will then shift where the arguments to the method are, we then
+ * change the start of the method to copy the arguments to the method
+ * into the place where the rest of the method's code expects them to be.
+ * Call allocateTemporaryVRegs(n), then use getTemporaryVReg(n),
+ * and then make sure finishedUsingTemporaryVRegs() is called!
+ */
+ public void allocateTemporaryVRegs(int count) {
+ if (count > tempVRegsAllocated) {
+ if (tempVRegsAllocated == 0) {
+ Log.info("Allocating temporary vregs for method...");
+ initialTempVReg = registersSize;
+ originalInVReg = (short) (registersSize - insSize);
+ } else {
+ Log.info("Extending allocation of temporary vregs for method...");
+ }
+ registersSize = (short) (initialTempVReg + count);
+ if (outsSize < count) {
+ outsSize = (short) count;
+ }
+ vregsNeedCopying = true;
+ tempVRegsAllocated = (short) count;
+ }
+ }
+
+ public int getTemporaryVReg(int number) {
+ if (number >= tempVRegsAllocated) {
+ Log.errorAndQuit("Not allocated enough temporary vregs!");
+ }
+ return initialTempVReg + number;
+ }
+
+ public void finishedUsingTemporaryVRegs() {
+ if (tempVRegsAllocated > 0 && vregsNeedCopying) {
+ // Just delete all the move instructions and generate again, if we already have some.
+ while (numMoveInsnsGenerated > 0) {
+ deleteInstruction(0);
+ numMoveInsnsGenerated--;
+ }
+
+ Log.info("Moving 'in' vregs to correct locations after allocating temporary vregs");
+
+ int shortyIdx = 0;
+ if (isStatic) {
+ shortyIdx = 1;
+ }
+
+ int insertionCounter = 0;
+
+ // Insert copy insns that move all the in VRs down.
+ for (int i = 0; i < insSize; i++) {
+ MInsn moveInsn = new MInsn();
+ moveInsn.insn = new Instruction();
+ moveInsn.insn.vregA = originalInVReg + i;
+ moveInsn.insn.vregB = originalInVReg + i + tempVRegsAllocated;
+
+ char type = 'L';
+ if (shortyIdx > 0) {
+ type = shorty.charAt(shortyIdx);
+ }
+ shortyIdx++;
+
+ if (type == 'L') {
+ moveInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_OBJECT_16);
+ } else if (type == 'D' || type == 'J') {
+ moveInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_WIDE_16);
+ i++;
+ } else {
+ moveInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_16);
+ }
+
+ insertInstructionAt(moveInsn, insertionCounter);
+ insertionCounter++;
+ Log.info("Temp vregs creation, Added instruction " + moveInsn);
+ numMoveInsnsGenerated++;
+ }
+
+ vregsNeedCopying = false;
+ }
+ }
+
+ /**
+ * When we insert new Field/Type/MethodIds into the DEX file, this may shunt some Ids
+ * into a new position in the table. If this happens, every reference to the Ids must
+ * be updated! Because CodeItems have their Instructions wrapped into a graph of MInsns
+ * during mutation, they don't have a view of all their instructions during mutation,
+ * and so if they are asked to update their instructions' indices into the tables, they
+ * must call this method to get the actual list of instructions they currently own.
+ */
+ public List<Instruction> requestLatestInstructions() {
+ List<Instruction> latestInsns = new ArrayList<Instruction>();
+ for (MInsn mInsn : mutatableInsns) {
+ latestInsns.add(mInsn.insn);
+ }
+ return latestInsns;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/Mutation.java b/tools/dexfuzz/src/dexfuzz/program/Mutation.java
new file mode 100644
index 0000000..2eba718
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/Mutation.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program;
+
+import dexfuzz.program.mutators.CodeMutator;
+
+/**
+ * Mutation should be subclassed by an AssociatedMutation in each CodeMutator,
+ * which will describe the parameters of the mutation, and override the getString()
+ * and parseString() methods here to allow serialization of the mutations.
+ */
+public abstract class Mutation {
+
+ public MutatableCode mutatableCode;
+
+ // The first field of any serialized mutation - the mutator that uses it.
+ public Class<? extends CodeMutator> mutatorClass;
+ // The second field of any serialized mutation...
+ // This is an index into the Program's list of MutatableCodes
+ // i.e., it is NOT an index into the DEX file's CodeItems!
+ public int mutatableCodeIdx;
+
+ public void setup(Class<? extends CodeMutator> mutatorClass, MutatableCode mutatableCode) {
+ this.mutatorClass = mutatorClass;
+ this.mutatableCode = mutatableCode;
+ this.mutatableCodeIdx = mutatableCode.mutatableCodeIdx;
+ }
+
+ public abstract String getString();
+
+ public abstract void parseString(String[] elements);
+} \ No newline at end of file
diff --git a/tools/dexfuzz/src/dexfuzz/program/MutationSerializer.java b/tools/dexfuzz/src/dexfuzz/program/MutationSerializer.java
new file mode 100644
index 0000000..7f79517
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/MutationSerializer.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program;
+
+import dexfuzz.Log;
+import dexfuzz.program.mutators.CodeMutator;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+
+/**
+ * Responsible for serializing mutations, allowing replay of mutations, and searching
+ * for a minimal set of mutations.
+ */
+public class MutationSerializer {
+ public static String getMutationString(Mutation mutation) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(mutation.mutatorClass.getCanonicalName()).append(" ");
+ builder.append(mutation.mutatableCodeIdx).append(" ");
+ builder.append(mutation.getString());
+ return builder.toString();
+ }
+
+ public static void writeMutation(BufferedWriter writer, Mutation mutation) throws IOException {
+ // Write out the common fields.
+ writer.write(mutation.mutatorClass.getCanonicalName() + " "
+ + mutation.mutatableCodeIdx + " ");
+
+ // Use the mutation's own function to write out the rest of the fields.
+ writer.write(mutation.getString() + "\n");
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Mutation readMutation(BufferedReader reader) throws IOException {
+ String line = reader.readLine();
+ String[] fields = null;
+ if (line != null) {
+ fields = line.split(" ");
+ } else {
+ Log.errorAndQuit("Could not read line during mutation loading.");
+ }
+
+ // Read the mutator's class name
+ String mutatorClassName = fields[0];
+
+ // Get the class for that mutator
+ Class<? extends CodeMutator> mutatorClass = null;
+ try {
+ mutatorClass = (Class<? extends CodeMutator>) Class.forName(mutatorClassName);
+ } catch (ClassNotFoundException e) {
+ Log.errorAndQuit("Cannot find a mutator class called: " + mutatorClassName);
+ }
+
+ Mutation mutation = null;
+ try {
+ mutation = mutatorClass.newInstance().getNewMutation();
+ } catch (InstantiationException e) {
+ Log.errorAndQuit("Unable to instantiate " + mutatorClassName
+ + " using default constructor.");
+ } catch (IllegalAccessException e) {
+ Log.errorAndQuit("Unable to access methods in " + mutatorClassName + ".");
+ }
+
+ if (mutation == null) {
+ Log.errorAndQuit("Unable to get Mutation for Mutator: " + mutatorClassName);
+ }
+
+ // Populate the common fields of the mutation.
+ mutation.mutatorClass = mutatorClass;
+ // The Program must set this later, using the mutatable_code_idx
+ // into its list of MutatableCodes.
+ mutation.mutatableCode = null;
+ mutation.mutatableCodeIdx = Integer.parseInt(fields[1]);
+
+ // Use the mutation's own method to read the rest of the fields.
+ mutation.parseString(fields);
+
+ return mutation;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/Program.java b/tools/dexfuzz/src/dexfuzz/program/Program.java
new file mode 100644
index 0000000..286fe52
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/Program.java
@@ -0,0 +1,602 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.Options;
+import dexfuzz.listeners.BaseListener;
+import dexfuzz.program.mutators.ArithOpChanger;
+import dexfuzz.program.mutators.BranchShifter;
+import dexfuzz.program.mutators.CmpBiasChanger;
+import dexfuzz.program.mutators.CodeMutator;
+import dexfuzz.program.mutators.ConstantValueChanger;
+import dexfuzz.program.mutators.ConversionRepeater;
+import dexfuzz.program.mutators.FieldFlagChanger;
+import dexfuzz.program.mutators.InstructionDeleter;
+import dexfuzz.program.mutators.InstructionDuplicator;
+import dexfuzz.program.mutators.InstructionSwapper;
+import dexfuzz.program.mutators.NewMethodCaller;
+import dexfuzz.program.mutators.NonsenseStringPrinter;
+import dexfuzz.program.mutators.PoolIndexChanger;
+import dexfuzz.program.mutators.RandomInstructionGenerator;
+import dexfuzz.program.mutators.SwitchBranchShifter;
+import dexfuzz.program.mutators.TryBlockShifter;
+import dexfuzz.program.mutators.ValuePrinter;
+import dexfuzz.program.mutators.VRegChanger;
+import dexfuzz.rawdex.ClassDataItem;
+import dexfuzz.rawdex.ClassDefItem;
+import dexfuzz.rawdex.CodeItem;
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.EncodedField;
+import dexfuzz.rawdex.EncodedMethod;
+import dexfuzz.rawdex.FieldIdItem;
+import dexfuzz.rawdex.MethodIdItem;
+import dexfuzz.rawdex.ProtoIdItem;
+import dexfuzz.rawdex.RawDexFile;
+import dexfuzz.rawdex.TypeIdItem;
+import dexfuzz.rawdex.formats.ContainsPoolIndex.PoolIndexKind;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * After the raw DEX file has been parsed, it is passed into this class
+ * that represents the program in a mutatable form.
+ * The class uses a CodeTranslator to translate between the raw DEX form
+ * for a method, and the mutatable form. It also controls all CodeMutators,
+ * deciding which ones should be applied to each CodeItem.
+ */
+public class Program {
+ /**
+ * The RNG used during mutation.
+ */
+ private Random rng;
+
+ /**
+ * The seed that was given to the RNG.
+ */
+ public long rngSeed;
+
+ /**
+ * The parsed raw DEX file.
+ */
+ private RawDexFile rawDexFile;
+
+ /**
+ * The system responsible for translating from CodeItems to MutatableCode and vice-versa.
+ */
+ private CodeTranslator translator;
+
+ /**
+ * Responsible for adding new class ID items, method ID items, etc.
+ */
+ private IdCreator idCreator;
+
+ /**
+ * A list of all the MutatableCode that the CodeTranslator produced from
+ * CodeItems that are acceptable to mutate.
+ */
+ private List<MutatableCode> mutatableCodes;
+
+ /**
+ * A list of all MutatableCode items that were mutated when mutateTheProgram()
+ * was called. updateRawDexFile() will update the relevant CodeItems when called,
+ * and then clear this list.
+ */
+ private List<MutatableCode> mutatedCodes;
+
+ /**
+ * A list of all registered CodeMutators that this Program can use to mutate methods.
+ */
+ private List<CodeMutator> mutators;
+
+ /**
+ * Used if we're loading mutations from a file, so we can find the correct mutator.
+ */
+ private Map<Class<? extends CodeMutator>, CodeMutator> mutatorsLookupByClass;
+
+ /**
+ * Tracks mutation stats.
+ */
+ private MutationStats mutationStats;
+
+ /**
+ * A list of all mutations used for loading/dumping mutations from/to a file.
+ */
+ private List<Mutation> mutations;
+
+ /**
+ * The listener who is interested in events.
+ * May be a listener that is responsible for multiple listeners.
+ */
+ private BaseListener listener;
+
+ /**
+ * Given a maximum number of mutations that can be performed on a method, n,
+ * give up after attempting (n * this value) mutations for any method.
+ */
+ private static final int MAXIMUM_MUTATION_ATTEMPT_FACTOR = 10;
+
+ /**
+ * Construct the mutatable Program based on the raw DEX file that was parsed initially.
+ */
+ public Program(RawDexFile rawDexFile, List<Mutation> previousMutations,
+ BaseListener listener) {
+ this.listener = listener;
+
+ idCreator = new IdCreator(rawDexFile);
+
+ // Set up the RNG.
+ rng = new Random();
+ if (Options.usingProvidedSeed) {
+ rng.setSeed(Options.rngSeed);
+ rngSeed = Options.rngSeed;
+ } else {
+ long seed = System.currentTimeMillis();
+ listener.handleSeed(seed);
+ rng.setSeed(seed);
+ rngSeed = seed;
+ }
+
+ if (previousMutations != null) {
+ mutations = previousMutations;
+ } else {
+ // Allocate the mutations list.
+ mutations = new ArrayList<Mutation>();
+
+ // Read in the mutations if we need to.
+ if (Options.loadMutations) {
+ // Allocate the mutators lookup table.
+ mutatorsLookupByClass = new HashMap<Class<? extends CodeMutator>, CodeMutator>();
+ loadMutationsFromDisk(Options.loadMutationsFile);
+ }
+ }
+
+ // Allocate the mutators list.
+ mutators = new ArrayList<CodeMutator>();
+
+ this.rawDexFile = rawDexFile;
+
+ mutatableCodes = new ArrayList<MutatableCode>();
+ mutatedCodes = new ArrayList<MutatableCode>();
+
+ translator = new CodeTranslator();
+
+ mutationStats = new MutationStats();
+
+ // Register all the code mutators here.
+ registerMutator(new ArithOpChanger(rng, mutationStats, mutations));
+ registerMutator(new BranchShifter(rng, mutationStats, mutations));
+ registerMutator(new CmpBiasChanger(rng, mutationStats, mutations));
+ registerMutator(new ConstantValueChanger(rng, mutationStats, mutations));
+ registerMutator(new ConversionRepeater(rng, mutationStats, mutations));
+ registerMutator(new FieldFlagChanger(rng, mutationStats, mutations));
+ registerMutator(new InstructionDeleter(rng, mutationStats, mutations));
+ registerMutator(new InstructionDuplicator(rng, mutationStats, mutations));
+ registerMutator(new InstructionSwapper(rng, mutationStats, mutations));
+ registerMutator(new NewMethodCaller(rng, mutationStats, mutations));
+ registerMutator(new NonsenseStringPrinter(rng, mutationStats, mutations));
+ registerMutator(new PoolIndexChanger(rng, mutationStats, mutations));
+ registerMutator(new RandomInstructionGenerator(rng, mutationStats, mutations));
+ registerMutator(new SwitchBranchShifter(rng, mutationStats, mutations));
+ registerMutator(new TryBlockShifter(rng, mutationStats, mutations));
+ registerMutator(new ValuePrinter(rng, mutationStats, mutations));
+ registerMutator(new VRegChanger(rng, mutationStats, mutations));
+
+ associateClassDefsAndClassData();
+ associateCodeItemsWithMethodNames();
+
+ int codeItemIdx = 0;
+ for (CodeItem codeItem : rawDexFile.codeItems) {
+ if (legalToMutate(codeItem)) {
+ Log.debug("Legal to mutate code item " + codeItemIdx);
+ int mutatableCodeIdx = mutatableCodes.size();
+ mutatableCodes.add(translator.codeItemToMutatableCode(this, codeItem,
+ codeItemIdx, mutatableCodeIdx));
+ } else {
+ Log.debug("Not legal to mutate code item " + codeItemIdx);
+ }
+ codeItemIdx++;
+ }
+ }
+
+ private void registerMutator(CodeMutator mutator) {
+ if (mutator.canBeTriggered()) {
+ Log.debug("Registering mutator " + mutator.getClass().getSimpleName());
+ mutators.add(mutator);
+ }
+ if (Options.loadMutations) {
+ mutatorsLookupByClass.put(mutator.getClass(), mutator);
+ }
+ }
+
+ /**
+ * Associate ClassDefItem to a ClassDataItem and vice-versa.
+ * This is so when we're associating method names with code items,
+ * we can find the name of the class the method belongs to.
+ */
+ private void associateClassDefsAndClassData() {
+ for (ClassDefItem classDefItem : rawDexFile.classDefs) {
+ if (classDefItem.classDataOff.pointsToSomething()) {
+ ClassDataItem classDataItem = (ClassDataItem)
+ classDefItem.classDataOff.getPointedToItem();
+ classDataItem.meta.classDefItem = classDefItem;
+ classDefItem.meta.classDataItem = classDataItem;
+ }
+ }
+ }
+
+ /**
+ * For each CodeItem, find the name of the method the item represents.
+ * This is done to allow the filtering of mutating methods based on if
+ * they have the name *_MUTATE, but also for debugging info.
+ */
+ private void associateCodeItemsWithMethodNames() {
+ // Associate method names with codeItems.
+ for (ClassDataItem classDataItem : rawDexFile.classDatas) {
+
+ String className = "";
+ if (classDataItem.meta.classDefItem != null) {
+ int typeIdx = classDataItem.meta.classDefItem.classIdx;
+ TypeIdItem typeIdItem = rawDexFile.typeIds.get(typeIdx);
+ className = rawDexFile.stringDatas.get(typeIdItem.descriptorIdx).getString() + ".";
+ }
+
+ // Do direct methods...
+ // Track the current method index with this value, since the encoding in
+ // each EncodedMethod is the absolute index for the first EncodedMethod,
+ // and then relative index for the rest...
+ int methodIdx = 0;
+ for (EncodedMethod method : classDataItem.directMethods) {
+ methodIdx = associateMethod(method, methodIdx, className);
+ }
+ // Reset methodIdx for virtual methods...
+ methodIdx = 0;
+ for (EncodedMethod method : classDataItem.virtualMethods) {
+ methodIdx = associateMethod(method, methodIdx, className);
+ }
+ }
+ }
+
+ /**
+ * Associate the name of the provided method with its CodeItem, if it
+ * has one.
+ *
+ * @param methodIdx The method index of the last EncodedMethod that was handled in this class.
+ * @return The method index of the EncodedMethod that has just been handled in this class.
+ */
+ private int associateMethod(EncodedMethod method, int methodIdx, String className) {
+ if (!method.codeOff.pointsToSomething()) {
+ // This method doesn't have a code item, so we won't encounter it later.
+ return methodIdx;
+ }
+
+ // First method index is an absolute index.
+ // The rest are relative to the previous.
+ // (so if methodIdx is initialised to 0, this single line works)
+ methodIdx = methodIdx + method.methodIdxDiff;
+
+ // Get the name.
+ MethodIdItem methodIdItem = rawDexFile.methodIds.get(methodIdx);
+ ProtoIdItem protoIdItem = rawDexFile.protoIds.get(methodIdItem.protoIdx);
+ String shorty = rawDexFile.stringDatas.get(protoIdItem.shortyIdx).getString();
+ String methodName = className
+ + rawDexFile.stringDatas.get(methodIdItem.nameIdx).getString();
+
+ // Get the codeItem.
+ if (method.codeOff.getPointedToItem() instanceof CodeItem) {
+ CodeItem codeItem = (CodeItem) method.codeOff.getPointedToItem();
+ codeItem.meta.methodName = methodName;
+ codeItem.meta.shorty = shorty;
+ codeItem.meta.isStatic = method.isStatic();
+ } else {
+ Log.errorAndQuit("You've got an EncodedMethod that points to an Offsettable"
+ + " that does not contain a CodeItem");
+ }
+
+ return methodIdx;
+ }
+
+ /**
+ * Determine, based on the current options supplied to dexfuzz, as well as
+ * its capabilities, if the provided CodeItem can be mutated.
+ * @param codeItem The CodeItem we may wish to mutate.
+ * @return If the CodeItem can be mutated.
+ */
+ private boolean legalToMutate(CodeItem codeItem) {
+ if (!Options.mutateLimit) {
+ Log.debug("Mutating everything.");
+ return true;
+ }
+ if (codeItem.meta.methodName.endsWith("_MUTATE")) {
+ Log.debug("Code item marked with _MUTATE.");
+ return true;
+ }
+ Log.debug("Code item not marked with _MUTATE, but not mutating all code items.");
+ return false;
+ }
+
+ private int getNumberOfMutationsToPerform() {
+ // We want n mutations to be twice as likely as n+1 mutations.
+ //
+ // So if we have max 3,
+ // then 0 has 8 chances ("tickets"),
+ // 1 has 4 chances
+ // 2 has 2 chances
+ // and 3 has 1 chance
+
+ // Allocate the tickets
+ // n mutations need (2^(n+1) - 1) tickets
+ // e.g.
+ // 3 mutations => 15 tickets
+ // 4 mutations => 31 tickets
+ int tickets = (2 << Options.methodMutations) - 1;
+
+ // Pick the lucky ticket
+ int luckyTicket = rng.nextInt(tickets);
+
+ // The tickets are put into buckets with accordance with log-base-2.
+ // have to make sure it's luckyTicket + 1, because log(0) is undefined
+ // so:
+ // log_2(1) => 0
+ // log_2(2) => 1
+ // log_2(3) => 1
+ // log_2(4) => 2
+ // log_2(5) => 2
+ // log_2(6) => 2
+ // log_2(7) => 2
+ // log_2(8) => 3
+ // ...
+ // so to make the highest mutation value the rarest,
+ // subtract log_2(luckyTicket+1) from the maximum number
+ // log2(x) <=> 31 - Integer.numberOfLeadingZeros(x)
+ int luckyMutation = Options.methodMutations
+ - (31 - Integer.numberOfLeadingZeros(luckyTicket + 1));
+
+ return luckyMutation;
+ }
+
+ /**
+ * Returns true if we completely failed to mutate this method's mutatable code after
+ * attempting to.
+ */
+ private boolean mutateAMutatableCode(MutatableCode mutatableCode) {
+ int mutations = getNumberOfMutationsToPerform();
+
+ Log.info("Attempting " + mutations + " mutations for method " + mutatableCode.name);
+
+ int mutationsApplied = 0;
+
+ int maximumMutationAttempts = Options.methodMutations * MAXIMUM_MUTATION_ATTEMPT_FACTOR;
+ int mutationAttempts = 0;
+ boolean hadToBail = false;
+
+ while (mutationsApplied < mutations) {
+ int mutatorIdx = rng.nextInt(mutators.size());
+ CodeMutator mutator = mutators.get(mutatorIdx);
+ Log.info("Running mutator " + mutator.getClass().getSimpleName());
+ if (mutator.attemptToMutate(mutatableCode)) {
+ mutationsApplied++;
+ }
+ mutationAttempts++;
+ if (mutationAttempts > maximumMutationAttempts) {
+ Log.info("Bailing out on mutation for this method, tried too many times...");
+ hadToBail = true;
+ break;
+ }
+ }
+
+ // If any of them actually mutated it, excellent!
+ if (mutationsApplied > 0) {
+ Log.info("Method was mutated.");
+ mutatedCodes.add(mutatableCode);
+ } else {
+ Log.info("Method was not mutated.");
+ }
+
+ return ((mutationsApplied == 0) && hadToBail);
+ }
+
+ /**
+ * Go through each mutatable method in turn, and attempt to mutate it.
+ * Afterwards, call updateRawDexFile() to apply the results of mutation to the
+ * original code.
+ */
+ public void mutateTheProgram() {
+ if (Options.loadMutations) {
+ applyMutationsFromList();
+ return;
+ }
+
+ // Typically, this is 2 to 10...
+ int methodsToMutate = Options.minMethods
+ + rng.nextInt((Options.maxMethods - Options.minMethods) + 1);
+
+ // Check we aren't trying to mutate more methods than we have.
+ if (methodsToMutate > mutatableCodes.size()) {
+ methodsToMutate = mutatableCodes.size();
+ }
+
+ // Check if we're going to end up mutating all the methods.
+ if (methodsToMutate == mutatableCodes.size()) {
+ // Just do them all in order.
+ Log.info("Mutating all possible methods.");
+ for (MutatableCode mutatableCode : mutatableCodes) {
+ if (mutatableCode == null) {
+ Log.errorAndQuit("Why do you have a null MutatableCode?");
+ }
+ mutateAMutatableCode(mutatableCode);
+ }
+ Log.info("Finished mutating all possible methods.");
+ } else {
+ // Pick them at random.
+ Log.info("Randomly selecting " + methodsToMutate + " methods to mutate.");
+ while (mutatedCodes.size() < methodsToMutate) {
+ int randomMethodIdx = rng.nextInt(mutatableCodes.size());
+ MutatableCode mutatableCode = mutatableCodes.get(randomMethodIdx);
+ if (mutatableCode == null) {
+ Log.errorAndQuit("Why do you have a null MutatableCode?");
+ }
+ if (!mutatedCodes.contains(mutatableCode)) {
+ boolean completelyFailedToMutate = mutateAMutatableCode(mutatableCode);
+ if (completelyFailedToMutate) {
+ methodsToMutate--;
+ }
+ }
+ }
+ Log.info("Finished mutating the methods.");
+ }
+
+ listener.handleMutationStats(mutationStats.getStatsString());
+
+ if (Options.dumpMutations) {
+ writeMutationsToDisk(Options.dumpMutationsFile);
+ }
+ }
+
+ private void writeMutationsToDisk(String fileName) {
+ Log.debug("Writing mutations to disk.");
+ try {
+ BufferedWriter writer = new BufferedWriter(new FileWriter(fileName));
+ for (Mutation mutation : mutations) {
+ MutationSerializer.writeMutation(writer, mutation);
+ }
+ writer.close();
+ } catch (IOException e) {
+ Log.errorAndQuit("IOException while writing mutations to disk...");
+ }
+ }
+
+ private void loadMutationsFromDisk(String fileName) {
+ Log.debug("Loading mutations from disk.");
+ try {
+ BufferedReader reader = new BufferedReader(new FileReader(fileName));
+ while (reader.ready()) {
+ Mutation mutation = MutationSerializer.readMutation(reader);
+ mutations.add(mutation);
+ }
+ reader.close();
+ } catch (IOException e) {
+ Log.errorAndQuit("IOException while loading mutations from disk...");
+ }
+ }
+
+ private void applyMutationsFromList() {
+ Log.info("Applying preloaded list of mutations...");
+ for (Mutation mutation : mutations) {
+ // Repopulate the MutatableCode field from the recorded index into the Program's list.
+ mutation.mutatableCode = mutatableCodes.get(mutation.mutatableCodeIdx);
+
+ // Get the right mutator.
+ CodeMutator mutator = mutatorsLookupByClass.get(mutation.mutatorClass);
+
+ // Apply the mutation.
+ mutator.forceMutate(mutation);
+
+ // Add this mutatable code to the list of mutated codes, if we haven't already.
+ if (!mutatedCodes.contains(mutation.mutatableCode)) {
+ mutatedCodes.add(mutation.mutatableCode);
+ }
+ }
+ Log.info("...finished applying preloaded list of mutations.");
+ }
+
+ public List<Mutation> getMutations() {
+ return mutations;
+ }
+
+ /**
+ * Updates any CodeItems that need to be updated after mutation.
+ */
+ public boolean updateRawDexFile() {
+ boolean anythingMutated = !(mutatedCodes.isEmpty());
+ for (MutatableCode mutatedCode : mutatedCodes) {
+ translator.mutatableCodeToCodeItem(rawDexFile.codeItems
+ .get(mutatedCode.codeItemIdx), mutatedCode);
+ }
+ mutatedCodes.clear();
+ return anythingMutated;
+ }
+
+ public void writeRawDexFile(DexRandomAccessFile file) throws IOException {
+ rawDexFile.write(file);
+ }
+
+ public void updateRawDexFileHeader(DexRandomAccessFile file) throws IOException {
+ rawDexFile.updateHeader(file);
+ }
+
+ /**
+ * Used by the CodeMutators to determine legal index values.
+ */
+ public int getTotalPoolIndicesByKind(PoolIndexKind poolIndexKind) {
+ switch (poolIndexKind) {
+ case Type:
+ return rawDexFile.typeIds.size();
+ case Field:
+ return rawDexFile.fieldIds.size();
+ case String:
+ return rawDexFile.stringIds.size();
+ case Method:
+ return rawDexFile.methodIds.size();
+ case Invalid:
+ return 0;
+ default:
+ }
+ return 0;
+ }
+
+ /**
+ * Used by the CodeMutators to lookup and/or create Ids.
+ */
+ public IdCreator getNewItemCreator() {
+ return idCreator;
+ }
+
+ /**
+ * Used by FieldFlagChanger, to find an EncodedField for a specified field in an insn,
+ * if that field is actually defined in this DEX file. If not, null is returned.
+ */
+ public EncodedField getEncodedField(int fieldIdx) {
+ if (fieldIdx >= rawDexFile.fieldIds.size()) {
+ Log.debug(String.format("Field idx 0x%x specified is not defined in this DEX file.",
+ fieldIdx));
+ return null;
+ }
+ FieldIdItem fieldId = rawDexFile.fieldIds.get(fieldIdx);
+
+ for (ClassDefItem classDef : rawDexFile.classDefs) {
+ if (classDef.classIdx == fieldId.classIdx) {
+ ClassDataItem classData = classDef.meta.classDataItem;
+ return classData.getEncodedFieldWithIndex(fieldIdx);
+ }
+ }
+
+ Log.debug(String.format("Field idx 0x%x specified is not defined in this DEX file.",
+ fieldIdx));
+ return null;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/ArithOpChanger.java b/tools/dexfuzz/src/dexfuzz/program/mutators/ArithOpChanger.java
new file mode 100644
index 0000000..4c69694
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/ArithOpChanger.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.program.MInsn;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+import dexfuzz.rawdex.Instruction;
+import dexfuzz.rawdex.Opcode;
+import dexfuzz.rawdex.OpcodeInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class ArithOpChanger extends CodeMutator {
+ /**
+ * Every CodeMutator has an AssociatedMutation, representing the
+ * mutation that this CodeMutator can perform, to allow separate
+ * generateMutation() and applyMutation() phases, allowing serialization.
+ */
+ public static class AssociatedMutation extends Mutation {
+ public int arithmeticInsnIdx;
+ public int newOpcode;
+
+ @Override
+ public String getString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(arithmeticInsnIdx).append(" ");
+ builder.append(newOpcode);
+ return builder.toString();
+ }
+
+ @Override
+ public void parseString(String[] elements) {
+ arithmeticInsnIdx = Integer.parseInt(elements[2]);
+ newOpcode = Integer.parseInt(elements[3]);
+ }
+ }
+
+ // The following two methods are here for the benefit of MutationSerializer,
+ // so it can create a CodeMutator and get the correct associated Mutation, as it
+ // reads in mutations from a dump of mutations.
+ @Override
+ public Mutation getNewMutation() {
+ return new AssociatedMutation();
+ }
+
+ public ArithOpChanger() { }
+
+ public ArithOpChanger(Random rng, MutationStats stats, List<Mutation> mutations) {
+ super(rng, stats, mutations);
+ likelihood = 75;
+ }
+
+ // A cache that should only exist between generateMutation() and applyMutation(),
+ // or be created at the start of applyMutation(), if we're reading in mutations from
+ // a file.
+ private List<MInsn> arithmeticInsns = null;
+
+ private void generateCachedArithmeticInsns(MutatableCode mutatableCode) {
+ if (arithmeticInsns != null) {
+ return;
+ }
+
+ arithmeticInsns = new ArrayList<MInsn>();
+
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (isArithmeticOperation(mInsn)) {
+ arithmeticInsns.add(mInsn);
+ }
+ }
+ }
+
+ @Override
+ protected boolean canMutate(MutatableCode mutatableCode) {
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (isArithmeticOperation(mInsn)) {
+ return true;
+ }
+ }
+
+ Log.debug("No arithmetic operations in method, skipping...");
+ return false;
+ }
+
+ @Override
+ protected Mutation generateMutation(MutatableCode mutatableCode) {
+ generateCachedArithmeticInsns(mutatableCode);
+
+ int arithmeticInsnIdx = rng.nextInt(arithmeticInsns.size());
+
+ MInsn randomInsn = arithmeticInsns.get(arithmeticInsnIdx);
+
+ OpcodeInfo oldOpcodeInfo = randomInsn.insn.info;
+
+ OpcodeInfo newOpcodeInfo = oldOpcodeInfo;
+
+ while (newOpcodeInfo.value == oldOpcodeInfo.value) {
+ newOpcodeInfo = Instruction.getOpcodeInfo(getLegalDifferentOpcode(randomInsn));
+ }
+
+ AssociatedMutation mutation = new AssociatedMutation();
+ mutation.setup(this.getClass(), mutatableCode);
+ mutation.arithmeticInsnIdx = arithmeticInsnIdx;
+ mutation.newOpcode = newOpcodeInfo.value;
+ return mutation;
+ }
+
+ @Override
+ protected void applyMutation(Mutation uncastMutation) {
+ // Cast the Mutation to our AssociatedMutation, so we can access its fields.
+ AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
+ MutatableCode mutatableCode = mutation.mutatableCode;
+
+ generateCachedArithmeticInsns(mutatableCode);
+
+ MInsn randomInsn = arithmeticInsns.get(mutation.arithmeticInsnIdx);
+
+ String oldInsnString = randomInsn.toString();
+
+ OpcodeInfo newOpcodeInfo = Instruction.getOpcodeInfo(mutation.newOpcode);
+
+ randomInsn.insn.info = newOpcodeInfo;
+
+ Log.info("Changed " + oldInsnString + " to " + randomInsn);
+
+ stats.incrementStat("Changed arithmetic opcode");
+
+ // Clear the cache.
+ arithmeticInsns = null;
+ }
+
+ private boolean isArithmeticOperation(MInsn mInsn) {
+ Opcode opcode = mInsn.insn.info.opcode;
+ if (Opcode.isBetween(opcode, Opcode.ADD_INT, Opcode.USHR_INT_LIT8)) {
+ return true;
+ }
+ return false;
+ }
+
+ private Opcode getLegalDifferentOpcode(MInsn mInsn) {
+ Opcode opcode = mInsn.insn.info.opcode;
+
+ for (List<Opcode> opcodeList : opcodeLists) {
+ Opcode first = opcodeList.get(0);
+ Opcode last = opcodeList.get(opcodeList.size() - 1);
+ if (Opcode.isBetween(opcode, first, last)) {
+ int newOpcodeIdx = rng.nextInt(opcodeList.size());
+ return opcodeList.get(newOpcodeIdx);
+ }
+ }
+
+ return opcode;
+ }
+
+ private static List<Opcode> intOpcodes = new ArrayList<Opcode>();
+ private static List<Opcode> int2addrOpcodes = new ArrayList<Opcode>();
+ private static List<Opcode> longOpcodes = new ArrayList<Opcode>();
+ private static List<Opcode> long2addrOpcodes = new ArrayList<Opcode>();
+ private static List<Opcode> floatOpcodes = new ArrayList<Opcode>();
+ private static List<Opcode> float2addrOpcodes = new ArrayList<Opcode>();
+ private static List<Opcode> doubleOpcodes = new ArrayList<Opcode>();
+ private static List<Opcode> double2addrOpcodes = new ArrayList<Opcode>();
+ private static List<Opcode> intLit8Opcodes = new ArrayList<Opcode>();
+ private static List<Opcode> intLit16Opcodes = new ArrayList<Opcode>();
+ private static List<List<Opcode>> opcodeLists = new ArrayList<List<Opcode>>();
+
+ static {
+ intOpcodes.add(Opcode.ADD_INT);
+ intOpcodes.add(Opcode.SUB_INT);
+ intOpcodes.add(Opcode.MUL_INT);
+ intOpcodes.add(Opcode.DIV_INT);
+ intOpcodes.add(Opcode.REM_INT);
+ intOpcodes.add(Opcode.AND_INT);
+ intOpcodes.add(Opcode.OR_INT);
+ intOpcodes.add(Opcode.XOR_INT);
+ intOpcodes.add(Opcode.SHL_INT);
+ intOpcodes.add(Opcode.SHR_INT);
+ intOpcodes.add(Opcode.USHR_INT);
+
+ int2addrOpcodes.add(Opcode.ADD_INT_2ADDR);
+ int2addrOpcodes.add(Opcode.SUB_INT_2ADDR);
+ int2addrOpcodes.add(Opcode.MUL_INT_2ADDR);
+ int2addrOpcodes.add(Opcode.DIV_INT_2ADDR);
+ int2addrOpcodes.add(Opcode.REM_INT_2ADDR);
+ int2addrOpcodes.add(Opcode.AND_INT_2ADDR);
+ int2addrOpcodes.add(Opcode.OR_INT_2ADDR);
+ int2addrOpcodes.add(Opcode.XOR_INT_2ADDR);
+ int2addrOpcodes.add(Opcode.SHL_INT_2ADDR);
+ int2addrOpcodes.add(Opcode.SHR_INT_2ADDR);
+ int2addrOpcodes.add(Opcode.USHR_INT_2ADDR);
+
+ longOpcodes.add(Opcode.ADD_LONG);
+ longOpcodes.add(Opcode.SUB_LONG);
+ longOpcodes.add(Opcode.MUL_LONG);
+ longOpcodes.add(Opcode.DIV_LONG);
+ longOpcodes.add(Opcode.REM_LONG);
+ longOpcodes.add(Opcode.AND_LONG);
+ longOpcodes.add(Opcode.OR_LONG);
+ longOpcodes.add(Opcode.XOR_LONG);
+ longOpcodes.add(Opcode.SHL_LONG);
+ longOpcodes.add(Opcode.SHR_LONG);
+ longOpcodes.add(Opcode.USHR_LONG);
+
+ long2addrOpcodes.add(Opcode.ADD_LONG_2ADDR);
+ long2addrOpcodes.add(Opcode.SUB_LONG_2ADDR);
+ long2addrOpcodes.add(Opcode.MUL_LONG_2ADDR);
+ long2addrOpcodes.add(Opcode.DIV_LONG_2ADDR);
+ long2addrOpcodes.add(Opcode.REM_LONG_2ADDR);
+ long2addrOpcodes.add(Opcode.AND_LONG_2ADDR);
+ long2addrOpcodes.add(Opcode.OR_LONG_2ADDR);
+ long2addrOpcodes.add(Opcode.XOR_LONG_2ADDR);
+ long2addrOpcodes.add(Opcode.SHL_LONG_2ADDR);
+ long2addrOpcodes.add(Opcode.SHR_LONG_2ADDR);
+ long2addrOpcodes.add(Opcode.USHR_LONG_2ADDR);
+
+ floatOpcodes.add(Opcode.ADD_FLOAT);
+ floatOpcodes.add(Opcode.SUB_FLOAT);
+ floatOpcodes.add(Opcode.MUL_FLOAT);
+ floatOpcodes.add(Opcode.DIV_FLOAT);
+ floatOpcodes.add(Opcode.REM_FLOAT);
+
+ float2addrOpcodes.add(Opcode.ADD_FLOAT_2ADDR);
+ float2addrOpcodes.add(Opcode.SUB_FLOAT_2ADDR);
+ float2addrOpcodes.add(Opcode.MUL_FLOAT_2ADDR);
+ float2addrOpcodes.add(Opcode.DIV_FLOAT_2ADDR);
+ float2addrOpcodes.add(Opcode.REM_FLOAT_2ADDR);
+
+ doubleOpcodes.add(Opcode.ADD_DOUBLE);
+ doubleOpcodes.add(Opcode.SUB_DOUBLE);
+ doubleOpcodes.add(Opcode.MUL_DOUBLE);
+ doubleOpcodes.add(Opcode.DIV_DOUBLE);
+ doubleOpcodes.add(Opcode.REM_DOUBLE);
+
+ double2addrOpcodes.add(Opcode.ADD_DOUBLE_2ADDR);
+ double2addrOpcodes.add(Opcode.SUB_DOUBLE_2ADDR);
+ double2addrOpcodes.add(Opcode.MUL_DOUBLE_2ADDR);
+ double2addrOpcodes.add(Opcode.DIV_DOUBLE_2ADDR);
+ double2addrOpcodes.add(Opcode.REM_DOUBLE_2ADDR);
+
+ intLit8Opcodes.add(Opcode.ADD_INT_LIT8);
+ intLit8Opcodes.add(Opcode.RSUB_INT_LIT8);
+ intLit8Opcodes.add(Opcode.MUL_INT_LIT8);
+ intLit8Opcodes.add(Opcode.DIV_INT_LIT8);
+ intLit8Opcodes.add(Opcode.REM_INT_LIT8);
+ intLit8Opcodes.add(Opcode.AND_INT_LIT8);
+ intLit8Opcodes.add(Opcode.OR_INT_LIT8);
+ intLit8Opcodes.add(Opcode.XOR_INT_LIT8);
+ intLit8Opcodes.add(Opcode.SHL_INT_LIT8);
+ intLit8Opcodes.add(Opcode.SHR_INT_LIT8);
+ intLit8Opcodes.add(Opcode.USHR_INT_LIT8);
+
+ intLit16Opcodes.add(Opcode.ADD_INT_LIT16);
+ intLit16Opcodes.add(Opcode.RSUB_INT);
+ intLit16Opcodes.add(Opcode.MUL_INT_LIT16);
+ intLit16Opcodes.add(Opcode.DIV_INT_LIT16);
+ intLit16Opcodes.add(Opcode.REM_INT_LIT16);
+ intLit16Opcodes.add(Opcode.AND_INT_LIT16);
+ intLit16Opcodes.add(Opcode.OR_INT_LIT16);
+ intLit16Opcodes.add(Opcode.XOR_INT_LIT16);
+
+ opcodeLists.add(intOpcodes);
+ opcodeLists.add(longOpcodes);
+ opcodeLists.add(floatOpcodes);
+ opcodeLists.add(doubleOpcodes);
+ opcodeLists.add(int2addrOpcodes);
+ opcodeLists.add(long2addrOpcodes);
+ opcodeLists.add(float2addrOpcodes);
+ opcodeLists.add(double2addrOpcodes);
+ opcodeLists.add(intLit8Opcodes);
+ opcodeLists.add(intLit16Opcodes);
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/BranchShifter.java b/tools/dexfuzz/src/dexfuzz/program/mutators/BranchShifter.java
new file mode 100644
index 0000000..a28f5ba
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/BranchShifter.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.program.MBranchInsn;
+import dexfuzz.program.MInsn;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class BranchShifter extends CodeMutator {
+ /**
+ * Every CodeMutator has an AssociatedMutation, representing the
+ * mutation that this CodeMutator can perform, to allow separate
+ * generateMutation() and applyMutation() phases, allowing serialization.
+ */
+ public static class AssociatedMutation extends Mutation {
+ public int branchInsnIdx;
+ public int newTargetIdx;
+
+ @Override
+ public String getString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(branchInsnIdx).append(" ");
+ builder.append(newTargetIdx);
+ return builder.toString();
+ }
+
+ @Override
+ public void parseString(String[] elements) {
+ branchInsnIdx = Integer.parseInt(elements[2]);
+ newTargetIdx = Integer.parseInt(elements[3]);
+ }
+ }
+
+ // The following two methods are here for the benefit of MutationSerializer,
+ // so it can create a CodeMutator and get the correct associated Mutation, as it
+ // reads in mutations from a dump of mutations.
+ @Override
+ public Mutation getNewMutation() {
+ return new AssociatedMutation();
+ }
+
+ public BranchShifter() { }
+
+ public BranchShifter(Random rng, MutationStats stats, List<Mutation> mutations) {
+ super(rng, stats, mutations);
+ likelihood = 30;
+ }
+
+ // A cache that should only exist between generateMutation() and applyMutation(),
+ // or be created at the start of applyMutation(), if we're reading in mutations from
+ // a file.
+ private List<MBranchInsn> branchInsns;
+
+ private void generateCachedBranchInsns(MutatableCode mutatableCode) {
+ if (branchInsns != null) {
+ return;
+ }
+
+ branchInsns = new ArrayList<MBranchInsn>();
+
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (mInsn instanceof MBranchInsn) {
+ branchInsns.add((MBranchInsn) mInsn);
+ }
+ }
+ }
+
+ @Override
+ protected boolean canMutate(MutatableCode mutatableCode) {
+ // Can't shift a branch if there's only one instruction in the method.
+ if (mutatableCode.getInstructionCount() == 1) {
+ Log.debug("Method contains only one instruction, skipping.");
+ return false;
+ }
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (mInsn instanceof MBranchInsn) {
+ return true;
+ }
+ }
+
+ Log.debug("Method contains no branch instructions.");
+ return false;
+ }
+
+ @Override
+ protected Mutation generateMutation(MutatableCode mutatableCode) {
+ generateCachedBranchInsns(mutatableCode);
+
+ // Pick a random branching instruction.
+ int branchInsnIdx = rng.nextInt(branchInsns.size());
+ MBranchInsn branchInsn = branchInsns.get(branchInsnIdx);
+
+ // Get its original target, find its index.
+ MInsn oldTargetInsn = branchInsn.target;
+ int oldTargetInsnIdx = mutatableCode.getInstructionIndex(oldTargetInsn);
+
+ int newTargetIdx = oldTargetInsnIdx;
+
+ int delta = 0;
+
+ // Keep searching for a new index.
+ while (newTargetIdx == oldTargetInsnIdx) {
+ // Vary by +/- 2 instructions.
+ delta = 0;
+ while (delta == 0) {
+ delta = (rng.nextInt(5) - 2);
+ }
+
+ newTargetIdx = oldTargetInsnIdx + delta;
+
+ // Check the new index is legal.
+ if (newTargetIdx < 0) {
+ newTargetIdx = 0;
+ } else if (newTargetIdx >= mutatableCode.getInstructionCount()) {
+ newTargetIdx = mutatableCode.getInstructionCount() - 1;
+ }
+ }
+
+ AssociatedMutation mutation = new AssociatedMutation();
+ mutation.setup(this.getClass(), mutatableCode);
+ mutation.branchInsnIdx = branchInsnIdx;
+ mutation.newTargetIdx = newTargetIdx;
+ return mutation;
+ }
+
+ @Override
+ protected void applyMutation(Mutation uncastMutation) {
+ // Cast the Mutation to our AssociatedMutation, so we can access its fields.
+ AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
+ MutatableCode mutatableCode = mutation.mutatableCode;
+
+ generateCachedBranchInsns(mutatableCode);
+
+ MBranchInsn branchInsn = branchInsns.get(mutation.branchInsnIdx);
+
+ // Get the new target.
+ MInsn newTargetInsn = mutatableCode.getInstructionAt(mutation.newTargetIdx);
+
+ // Set the new target.
+ branchInsn.target = newTargetInsn;
+
+ Log.info("Shifted the target of " + branchInsn + " to point to " + newTargetInsn);
+
+ stats.incrementStat("Shifted branch target");
+
+ // Clear cache.
+ branchInsns = null;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/CmpBiasChanger.java b/tools/dexfuzz/src/dexfuzz/program/mutators/CmpBiasChanger.java
new file mode 100644
index 0000000..dc60e79
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/CmpBiasChanger.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.program.MInsn;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+import dexfuzz.rawdex.Instruction;
+import dexfuzz.rawdex.Opcode;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class CmpBiasChanger extends CodeMutator {
+ /**
+ * Every CodeMutator has an AssociatedMutation, representing the
+ * mutation that this CodeMutator can perform, to allow separate
+ * generateMutation() and applyMutation() phases, allowing serialization.
+ */
+ public static class AssociatedMutation extends Mutation {
+ public int cmpBiasInsnIdx;
+
+ @Override
+ public String getString() {
+ return Integer.toString(cmpBiasInsnIdx);
+ }
+
+ @Override
+ public void parseString(String[] elements) {
+ cmpBiasInsnIdx = Integer.parseInt(elements[2]);
+ }
+ }
+
+ // The following two methods are here for the benefit of MutationSerializer,
+ // so it can create a CodeMutator and get the correct associated Mutation, as it
+ // reads in mutations from a dump of mutations.
+ @Override
+ public Mutation getNewMutation() {
+ return new AssociatedMutation();
+ }
+
+ public CmpBiasChanger() { }
+
+ public CmpBiasChanger(Random rng, MutationStats stats, List<Mutation> mutations) {
+ super(rng, stats, mutations);
+ likelihood = 30;
+ }
+
+ // A cache that should only exist between generateMutation() and applyMutation(),
+ // or be created at the start of applyMutation(), if we're reading in mutations from
+ // a file.
+ private List<MInsn> cmpBiasInsns = null;
+
+ private void generateCachedCmpBiasInsns(MutatableCode mutatableCode) {
+ if (cmpBiasInsns != null) {
+ return;
+ }
+
+ cmpBiasInsns = new ArrayList<MInsn>();
+
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (isCmpBiasOperation(mInsn)) {
+ cmpBiasInsns.add(mInsn);
+ }
+ }
+ }
+
+ @Override
+ protected boolean canMutate(MutatableCode mutatableCode) {
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (isCmpBiasOperation(mInsn)) {
+ return true;
+ }
+ }
+
+ Log.debug("No cmp-with-bias operations in method, skipping...");
+ return false;
+ }
+
+ @Override
+ protected Mutation generateMutation(MutatableCode mutatableCode) {
+ generateCachedCmpBiasInsns(mutatableCode);
+
+ int cmpBiasInsnIdx = rng.nextInt(cmpBiasInsns.size());
+
+ AssociatedMutation mutation = new AssociatedMutation();
+ mutation.setup(this.getClass(), mutatableCode);
+ mutation.cmpBiasInsnIdx = cmpBiasInsnIdx;
+ return mutation;
+ }
+
+ @Override
+ protected void applyMutation(Mutation uncastMutation) {
+ // Cast the Mutation to our AssociatedMutation, so we can access its fields.
+ AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
+ MutatableCode mutatableCode = mutation.mutatableCode;
+
+ generateCachedCmpBiasInsns(mutatableCode);
+
+ MInsn cmpBiasInsn = cmpBiasInsns.get(mutation.cmpBiasInsnIdx);
+
+ String oldInsnString = cmpBiasInsn.toString();
+
+ Opcode newOpcode = getLegalDifferentOpcode(cmpBiasInsn);
+
+ cmpBiasInsn.insn.info = Instruction.getOpcodeInfo(newOpcode);
+
+ Log.info("Changed " + oldInsnString + " to " + cmpBiasInsn);
+
+ stats.incrementStat("Changed comparison bias");
+
+ // Clear cache.
+ cmpBiasInsns = null;
+ }
+
+ private Opcode getLegalDifferentOpcode(MInsn mInsn) {
+ Opcode opcode = mInsn.insn.info.opcode;
+ if (opcode == Opcode.CMPG_DOUBLE) {
+ return Opcode.CMPL_DOUBLE;
+ }
+ if (opcode == Opcode.CMPL_DOUBLE) {
+ return Opcode.CMPG_DOUBLE;
+ }
+ if (opcode == Opcode.CMPG_FLOAT) {
+ return Opcode.CMPL_FLOAT;
+ }
+ return Opcode.CMPG_FLOAT;
+ }
+
+ private boolean isCmpBiasOperation(MInsn mInsn) {
+ Opcode opcode = mInsn.insn.info.opcode;
+ if (Opcode.isBetween(opcode, Opcode.CMPL_FLOAT, Opcode.CMPG_DOUBLE)) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/CodeMutator.java b/tools/dexfuzz/src/dexfuzz/program/mutators/CodeMutator.java
new file mode 100644
index 0000000..be566ad
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/CodeMutator.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.Options;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+
+import java.util.List;
+import java.util.Random;
+
+/**
+ * The base class for all classes that can mutate methods.
+ */
+public abstract class CodeMutator {
+ /**
+ * The RNG, passed in by the Program that initialised us.
+ */
+ protected Random rng;
+
+ /**
+ * Used to track which mutations happen.
+ */
+ protected MutationStats stats;
+
+ /**
+ * Used to track mutations that have been applied so far.
+ */
+ protected List<Mutation> mutations;
+
+ /**
+ * The chance, out of 100, that this mutator actually mutates the the program
+ * when asked to by the Program. The default is 50% chance, but each mutator that
+ * extends CodeMutator should its own default.
+ */
+ protected int likelihood = 50;
+
+ /**
+ * This constructor is only intended for use in MutationRecorder...
+ */
+ public CodeMutator() {
+
+ }
+
+ /**
+ * Constructor that all subclasses must call...
+ *
+ * @param rng The RNG that the Program created.
+ */
+ public CodeMutator(Random rng, MutationStats stats, List<Mutation> mutations) {
+ this.rng = rng;
+ this.stats = stats;
+ this.mutations = mutations;
+
+ String name = this.getClass().getSimpleName().toLowerCase();
+
+ if (Options.mutationLikelihoods.containsKey(name)) {
+ likelihood = Options.mutationLikelihoods.get(name);
+ Log.info("Set mutation likelihood to " + likelihood
+ + "% for " + this.getClass().getSimpleName());
+ }
+ }
+
+ /**
+ * When the Program picks a particular mutator to mutate the code, it calls
+ * this function, that determines if the mutator will actually mutate the code.
+ * If so, it then calls the mutationFunction() method, that every subclass CodeMutator
+ * is expected to implement to perform its mutation.
+ *
+ * @return If mutation took place.
+ */
+ public boolean attemptToMutate(MutatableCode mutatableCode) {
+ if (shouldMutate(mutatableCode)) {
+ generateAndApplyMutation(mutatableCode);
+ return true;
+ }
+ Log.info("Skipping mutation.");
+ return false;
+ }
+
+ public void forceMutate(Mutation mutation) {
+ Log.info("Forcing mutation.");
+ applyMutation(mutation);
+ }
+
+ public boolean canBeTriggered() {
+ return (likelihood > 0);
+ }
+
+ /**
+ * Randomly determine if the mutator will actually mutate a method, based on its
+ * provided likelihood of mutation.
+ *
+ * @return If the method should be mutated.
+ */
+ private boolean shouldMutate(MutatableCode mutatableCode) {
+ return ((rng.nextInt(100) < likelihood) && canMutate(mutatableCode));
+ }
+
+ private void generateAndApplyMutation(MutatableCode mutatableCode) {
+ Mutation mutation = generateMutation(mutatableCode);
+ // Always save the mutation.
+ mutations.add(mutation);
+ applyMutation(mutation);
+ }
+
+ /**
+ * A CodeMutator must override this method if there is any reason why could not mutate
+ * a particular method, and return false in that case.
+ */
+ protected boolean canMutate(MutatableCode mutatableCode) {
+ return true;
+ }
+
+ protected abstract Mutation generateMutation(MutatableCode mutatableCode);
+
+ protected abstract void applyMutation(Mutation uncastMutation);
+
+ public abstract Mutation getNewMutation();
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/ConstantValueChanger.java b/tools/dexfuzz/src/dexfuzz/program/mutators/ConstantValueChanger.java
new file mode 100644
index 0000000..22f04e8
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/ConstantValueChanger.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.program.MInsn;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+import dexfuzz.rawdex.formats.ContainsConst;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class ConstantValueChanger extends CodeMutator {
+ /**
+ * Every CodeMutator has an AssociatedMutation, representing the
+ * mutation that this CodeMutator can perform, to allow separate
+ * generateMutation() and applyMutation() phases, allowing serialization.
+ */
+ public static class AssociatedMutation extends Mutation {
+ public int constInsnIdx;
+ public long newConstant;
+
+ @Override
+ public String getString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(constInsnIdx).append(" ");
+ builder.append(newConstant);
+ return builder.toString();
+ }
+
+ @Override
+ public void parseString(String[] elements) {
+ constInsnIdx = Integer.parseInt(elements[2]);
+ newConstant = Long.parseLong(elements[3]);
+ }
+ }
+
+ // The following two methods are here for the benefit of MutationSerializer,
+ // so it can create a CodeMutator and get the correct associated Mutation, as it
+ // reads in mutations from a dump of mutations.
+ @Override
+ public Mutation getNewMutation() {
+ return new AssociatedMutation();
+ }
+
+ public ConstantValueChanger() { }
+
+ public ConstantValueChanger(Random rng, MutationStats stats, List<Mutation> mutations) {
+ super(rng, stats, mutations);
+ likelihood = 70;
+ }
+
+ // A cache that should only exist between generateMutation() and applyMutation(),
+ // or be created at the start of applyMutation(), if we're reading in mutations from
+ // a file.
+ private List<MInsn> constInsns = null;
+
+ private void generateCachedConstInsns(MutatableCode mutatableCode) {
+ if (constInsns != null) {
+ return;
+ }
+
+ constInsns = new ArrayList<MInsn>();
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (mInsn.insn.info.format instanceof ContainsConst) {
+ constInsns.add(mInsn);
+ }
+ }
+ }
+
+ @Override
+ protected boolean canMutate(MutatableCode mutatableCode) {
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (mInsn.insn.info.format instanceof ContainsConst) {
+ return true;
+ }
+ }
+
+ Log.debug("Method contains no const instructions.");
+ return false;
+ }
+
+ @Override
+ protected Mutation generateMutation(MutatableCode mutatableCode) {
+ generateCachedConstInsns(mutatableCode);
+
+ // Pick a random const instruction.
+ int constInsnIdx = rng.nextInt(constInsns.size());
+ MInsn constInsn = constInsns.get(constInsnIdx);
+
+ // Get the constant.
+ long oldConstant = ((ContainsConst)constInsn.insn.info.format).getConst(constInsn.insn);
+
+ long newConstant = oldConstant;
+
+ // Make a new constant.
+ while (newConstant == oldConstant) {
+ newConstant = rng.nextLong()
+ % ((ContainsConst)constInsn.insn.info.format).getConstRange();
+ }
+
+ AssociatedMutation mutation = new AssociatedMutation();
+ mutation.setup(this.getClass(), mutatableCode);
+ mutation.constInsnIdx = constInsnIdx;
+ mutation.newConstant = newConstant;
+ return mutation;
+ }
+
+ @Override
+ protected void applyMutation(Mutation uncastMutation) {
+ // Cast the Mutation to our AssociatedMutation, so we can access its fields.
+ AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
+ MutatableCode mutatableCode = mutation.mutatableCode;
+
+ generateCachedConstInsns(mutatableCode);
+
+ MInsn constInsn = constInsns.get(mutation.constInsnIdx);
+
+ long oldConstant = ((ContainsConst)constInsn.insn.info.format).getConst(constInsn.insn);
+
+ Log.info("Changed constant value #" + oldConstant + " to #" + mutation.newConstant
+ + " in " + constInsn);
+
+ stats.incrementStat("Changed constant value");
+
+ // Set the new constant.
+ ((ContainsConst)constInsn.insn.info.format).setConst(constInsn.insn, mutation.newConstant);
+
+ // Clear cache.
+ constInsns = null;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/ConversionRepeater.java b/tools/dexfuzz/src/dexfuzz/program/mutators/ConversionRepeater.java
new file mode 100644
index 0000000..7fdf304
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/ConversionRepeater.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.program.MInsn;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+import dexfuzz.rawdex.Instruction;
+import dexfuzz.rawdex.Opcode;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class ConversionRepeater extends CodeMutator {
+ /**
+ * Every CodeMutator has an AssociatedMutation, representing the
+ * mutation that this CodeMutator can perform, to allow separate
+ * generateMutation() and applyMutation() phases, allowing serialization.
+ */
+ public static class AssociatedMutation extends Mutation {
+ public int conversionInsnIdx;
+
+ @Override
+ public String getString() {
+ return Integer.toString(conversionInsnIdx);
+ }
+
+ @Override
+ public void parseString(String[] elements) {
+ conversionInsnIdx = Integer.parseInt(elements[2]);
+ }
+ }
+
+ // The following two methods are here for the benefit of MutationSerializer,
+ // so it can create a CodeMutator and get the correct associated Mutation, as it
+ // reads in mutations from a dump of mutations.
+ @Override
+ public Mutation getNewMutation() {
+ return new AssociatedMutation();
+ }
+
+ public ConversionRepeater() { }
+
+ public ConversionRepeater(Random rng, MutationStats stats, List<Mutation> mutations) {
+ super(rng, stats, mutations);
+ likelihood = 50;
+ }
+
+ // A cache that should only exist between generateMutation() and applyMutation(),
+ // or be created at the start of applyMutation(), if we're reading in mutations from
+ // a file.
+ private List<MInsn> conversionInsns = null;
+
+ private void generateCachedConversionInsns(MutatableCode mutatableCode) {
+ if (conversionInsns != null) {
+ return;
+ }
+
+ conversionInsns = new ArrayList<MInsn>();
+
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (isConversionInstruction(mInsn)) {
+ conversionInsns.add(mInsn);
+ }
+ }
+ }
+
+ @Override
+ protected boolean canMutate(MutatableCode mutatableCode) {
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (isConversionInstruction(mInsn)) {
+ return true;
+ }
+ }
+
+ Log.debug("No conversion operations in method, skipping...");
+ return false;
+ }
+
+ @Override
+ protected Mutation generateMutation(MutatableCode mutatableCode) {
+ generateCachedConversionInsns(mutatableCode);
+ int conversionInsnIdx = rng.nextInt(conversionInsns.size());
+ AssociatedMutation mutation = new AssociatedMutation();
+ mutation.setup(this.getClass(), mutatableCode);
+ mutation.conversionInsnIdx = conversionInsnIdx;
+ return mutation;
+ }
+
+ @Override
+ protected void applyMutation(Mutation uncastMutation) {
+ // Cast the Mutation to our AssociatedMutation, so we can access its fields.
+ AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
+ MutatableCode mutatableCode = mutation.mutatableCode;
+
+ generateCachedConversionInsns(mutatableCode);
+
+ MInsn originalInsn = conversionInsns.get(mutation.conversionInsnIdx);
+
+ // We want to create two new instructions:
+ // [original conversion] eg float-to-int
+ // NEW: [there] eg int-to-float (with vregs of first inst swapped)
+ // NEW: [back] eg float-to-int
+
+ // Create the "there" instruction.
+ MInsn newInsnThere = originalInsn.clone();
+
+ // Flip the opcode.
+ Opcode oppositeOpcode = null;
+ switch (newInsnThere.insn.info.opcode) {
+ case INT_TO_LONG:
+ oppositeOpcode = Opcode.LONG_TO_INT;
+ break;
+ case INT_TO_FLOAT:
+ oppositeOpcode = Opcode.FLOAT_TO_INT;
+ break;
+ case INT_TO_DOUBLE:
+ oppositeOpcode = Opcode.DOUBLE_TO_INT;
+ break;
+ case LONG_TO_INT:
+ oppositeOpcode = Opcode.INT_TO_LONG;
+ break;
+ case LONG_TO_FLOAT:
+ oppositeOpcode = Opcode.FLOAT_TO_LONG;
+ break;
+ case LONG_TO_DOUBLE:
+ oppositeOpcode = Opcode.DOUBLE_TO_LONG;
+ break;
+ case FLOAT_TO_INT:
+ oppositeOpcode = Opcode.INT_TO_FLOAT;
+ break;
+ case FLOAT_TO_LONG:
+ oppositeOpcode = Opcode.LONG_TO_FLOAT;
+ break;
+ case FLOAT_TO_DOUBLE:
+ oppositeOpcode = Opcode.DOUBLE_TO_FLOAT;
+ break;
+ case DOUBLE_TO_INT:
+ oppositeOpcode = Opcode.INT_TO_DOUBLE;
+ break;
+ case DOUBLE_TO_LONG:
+ oppositeOpcode = Opcode.LONG_TO_DOUBLE;
+ break;
+ case DOUBLE_TO_FLOAT:
+ oppositeOpcode = Opcode.FLOAT_TO_DOUBLE;
+ break;
+ default:
+ Log.errorAndQuit(
+ "Trying to repeat the conversion in an insn that is not a conversion insn.");
+ }
+ newInsnThere.insn.info = Instruction.getOpcodeInfo(oppositeOpcode);
+
+ // Swap the vregs.
+ long tempReg = newInsnThere.insn.vregA;
+ newInsnThere.insn.vregA = newInsnThere.insn.vregB;
+ newInsnThere.insn.vregB = tempReg;
+
+ // Create the "back" instruction.
+ MInsn newInsnBack = originalInsn.clone();
+
+ // Get the index into the MutatableCode's mInsns list for this insn.
+ int originalInsnIdx = mutatableCode.getInstructionIndex(originalInsn);
+
+ // Insert the new instructions.
+ mutatableCode.insertInstructionAfter(newInsnThere, originalInsnIdx);
+ mutatableCode.insertInstructionAfter(newInsnBack, originalInsnIdx + 1);
+
+ Log.info("Performing conversion repetition for " + originalInsn);
+
+ stats.incrementStat("Repeating conversion");
+
+ // Clear the cache.
+ conversionInsns = null;
+ }
+
+ private boolean isConversionInstruction(MInsn mInsn) {
+ Opcode opcode = mInsn.insn.info.opcode;
+ if (Opcode.isBetween(opcode, Opcode.INT_TO_LONG, Opcode.DOUBLE_TO_FLOAT)) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/FieldFlagChanger.java b/tools/dexfuzz/src/dexfuzz/program/mutators/FieldFlagChanger.java
new file mode 100644
index 0000000..0849d12
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/FieldFlagChanger.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.program.MInsn;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+import dexfuzz.rawdex.EncodedField;
+import dexfuzz.rawdex.Instruction;
+import dexfuzz.rawdex.Opcode;
+import dexfuzz.rawdex.formats.ContainsPoolIndex;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class FieldFlagChanger extends CodeMutator {
+ /**
+ * Every CodeMutator has an AssociatedMutation, representing the
+ * mutation that this CodeMutator can perform, to allow separate
+ * generateMutation() and applyMutation() phases, allowing serialization.
+ */
+ public static class AssociatedMutation extends Mutation {
+ public int fieldInsnIdx;
+ public boolean setVolatile;
+
+ @Override
+ public String getString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(fieldInsnIdx).append(" ");
+ builder.append(setVolatile);
+ return builder.toString();
+ }
+
+ @Override
+ public void parseString(String[] elements) {
+ fieldInsnIdx = Integer.parseInt(elements[2]);
+ setVolatile = Boolean.parseBoolean(elements[3]);
+ }
+ }
+
+ // The following two methods are here for the benefit of MutationSerializer,
+ // so it can create a CodeMutator and get the correct associated Mutation, as it
+ // reads in mutations from a dump of mutations.
+ @Override
+ public Mutation getNewMutation() {
+ return new AssociatedMutation();
+ }
+
+ public FieldFlagChanger() { }
+
+ public FieldFlagChanger(Random rng, MutationStats stats, List<Mutation> mutations) {
+ super(rng, stats, mutations);
+ likelihood = 40;
+ }
+
+ // A cache that should only exist between generateMutation() and applyMutation(),
+ // or be created at the start of applyMutation(), if we're reading in mutations from
+ // a file.
+ private List<MInsn> fieldInsns = null;
+
+ private void generateCachedFieldInsns(MutatableCode mutatableCode) {
+ if (fieldInsns != null) {
+ return;
+ }
+
+ fieldInsns = new ArrayList<MInsn>();
+
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (isFileDefinedFieldInstruction(mInsn, mutatableCode)) {
+ fieldInsns.add(mInsn);
+ }
+ }
+ }
+
+ @Override
+ protected boolean canMutate(MutatableCode mutatableCode) {
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (isFileDefinedFieldInstruction(mInsn, mutatableCode)) {
+ return true;
+ }
+ }
+
+ Log.debug("No field instructions in method, skipping...");
+ return false;
+ }
+
+ @Override
+ protected Mutation generateMutation(MutatableCode mutatableCode) {
+ generateCachedFieldInsns(mutatableCode);
+
+ int fieldInsnIdx = rng.nextInt(fieldInsns.size());
+
+ Instruction insn = fieldInsns.get(fieldInsnIdx).insn;
+ ContainsPoolIndex containsPoolIndex = (ContainsPoolIndex) insn.info.format;
+ int fieldIdx = containsPoolIndex.getPoolIndex(insn);
+ EncodedField encodedField = mutatableCode.program.getEncodedField(fieldIdx);
+
+ boolean setVolatile = false;
+ if (!encodedField.isVolatile()) {
+ setVolatile = true;
+ }
+ // TODO: Flip more flags?
+
+ AssociatedMutation mutation = new AssociatedMutation();
+ mutation.setup(this.getClass(), mutatableCode);
+ mutation.fieldInsnIdx = fieldInsnIdx;
+ mutation.setVolatile = setVolatile;
+ return mutation;
+ }
+
+ @Override
+ protected void applyMutation(Mutation uncastMutation) {
+ // Cast the Mutation to our AssociatedMutation, so we can access its fields.
+ AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
+ MutatableCode mutatableCode = mutation.mutatableCode;
+
+ generateCachedFieldInsns(mutatableCode);
+
+ Instruction insn = fieldInsns.get(mutation.fieldInsnIdx).insn;
+ ContainsPoolIndex containsPoolIndex = (ContainsPoolIndex) insn.info.format;
+ int fieldIdx = containsPoolIndex.getPoolIndex(insn);
+ EncodedField encodedField = mutatableCode.program.getEncodedField(fieldIdx);
+
+ if (mutation.setVolatile) {
+ encodedField.setVolatile(true);
+ Log.info("Set field idx " + fieldIdx + " as volatile");
+ } else {
+ encodedField.setVolatile(false);
+ Log.info("Set field idx " + fieldIdx + " as not volatile");
+ }
+
+ stats.incrementStat("Changed volatility of field");
+
+ // Clear cache.
+ fieldInsns = null;
+ }
+
+ private boolean isFileDefinedFieldInstruction(MInsn mInsn, MutatableCode mutatableCode) {
+ Opcode opcode = mInsn.insn.info.opcode;
+ if (Opcode.isBetween(opcode, Opcode.IGET, Opcode.SPUT_SHORT)) {
+ Instruction insn = mInsn.insn;
+ ContainsPoolIndex containsPoolIndex = (ContainsPoolIndex) insn.info.format;
+ int fieldIdx = containsPoolIndex.getPoolIndex(insn);
+ if (mutatableCode.program.getEncodedField(fieldIdx) != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/InstructionDeleter.java b/tools/dexfuzz/src/dexfuzz/program/mutators/InstructionDeleter.java
new file mode 100644
index 0000000..8ffa4c5
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/InstructionDeleter.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.program.MInsn;
+import dexfuzz.program.MInsnWithData;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+
+import java.util.List;
+import java.util.Random;
+
+public class InstructionDeleter extends CodeMutator {
+ /**
+ * Every CodeMutator has an AssociatedMutation, representing the
+ * mutation that this CodeMutator can perform, to allow separate
+ * generateMutation() and applyMutation() phases, allowing serialization.
+ */
+ public static class AssociatedMutation extends Mutation {
+ public int insnToDeleteIdx;
+
+ @Override
+ public String getString() {
+ return Integer.toString(insnToDeleteIdx);
+ }
+
+ @Override
+ public void parseString(String[] elements) {
+ insnToDeleteIdx = Integer.parseInt(elements[2]);
+ }
+ }
+
+ // The following two methods are here for the benefit of MutationSerializer,
+ // so it can create a CodeMutator and get the correct associated Mutation, as it
+ // reads in mutations from a dump of mutations.
+ @Override
+ public Mutation getNewMutation() {
+ return new AssociatedMutation();
+ }
+
+ public InstructionDeleter() { }
+
+ public InstructionDeleter(Random rng, MutationStats stats, List<Mutation> mutations) {
+ super(rng, stats, mutations);
+ likelihood = 40;
+ }
+
+ @Override
+ protected boolean canMutate(MutatableCode mutatableCode) {
+ if (mutatableCode.getInstructionCount() < 4) {
+ // TODO: Make this more sophisticated - right now this is to avoid problems with
+ // a method that has 3 instructions: fill-array-data; return-void; <data for fill-array-data>
+ Log.debug("Cannot delete insns in a method with only a few.");
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected Mutation generateMutation(MutatableCode mutatableCode) {
+ // Pick an instruction at random...
+ int insnIdx = rng.nextInt(mutatableCode.getInstructionCount());
+
+ AssociatedMutation mutation = new AssociatedMutation();
+ mutation.setup(this.getClass(), mutatableCode);
+ mutation.insnToDeleteIdx = insnIdx;
+ return mutation;
+ }
+
+ @Override
+ protected void applyMutation(Mutation uncastMutation) {
+ // Cast the Mutation to our AssociatedMutation, so we can access its fields.
+ AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
+ MutatableCode mutatableCode = mutation.mutatableCode;
+
+ MInsn toBeDeleted =
+ mutatableCode.getInstructionAt(mutation.insnToDeleteIdx);
+
+ Log.info("Deleting " + toBeDeleted);
+
+ stats.incrementStat("Deleted instruction");
+
+ // Delete the instruction.
+ mutatableCode.deleteInstruction(mutation.insnToDeleteIdx);
+
+ // If we delete a with-data insn, we should delete the associated data insn as well.
+ if (toBeDeleted instanceof MInsnWithData) {
+ Log.info(toBeDeleted + " had associated data, so the data insn was deleted.");
+ // Get the data instruction.
+ MInsn dataInsn =
+ ((MInsnWithData)toBeDeleted).dataTarget;
+ mutatableCode.deleteInstruction(dataInsn);
+ stats.incrementStat("Deleted a with-data insn's data");
+ }
+ // If we delete a data insn, we should delete the associated with-data insn as well.
+ if (toBeDeleted.insn.justRaw) {
+ // .justRaw implies that this is a data insn.
+ Log.info(toBeDeleted
+ + " was data, so the associated with-data insn was deleted.");
+
+ // First, find the with-data insn that is pointing to this insn.
+ MInsn withDataInsn = null;
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (mInsn instanceof MInsnWithData) {
+ if (((MInsnWithData)mInsn).dataTarget == toBeDeleted) {
+ withDataInsn = mInsn;
+ break;
+ }
+ }
+ }
+
+ // Now delete the with-data insn.
+ if (withDataInsn != null) {
+ mutatableCode.deleteInstruction(withDataInsn);
+ stats.incrementStat("Deleted a data insn's with-data insn");
+ } else {
+ Log.errorAndQuit("Tried to delete a data insn, "
+ + "but it didn't have any with-data insn pointing at it?");
+ }
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/InstructionDuplicator.java b/tools/dexfuzz/src/dexfuzz/program/mutators/InstructionDuplicator.java
new file mode 100644
index 0000000..4917056
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/InstructionDuplicator.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.program.MInsn;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+import dexfuzz.rawdex.Opcode;
+
+import java.util.List;
+import java.util.Random;
+
+public class InstructionDuplicator extends CodeMutator {
+ /**
+ * Every CodeMutator has an AssociatedMutation, representing the
+ * mutation that this CodeMutator can perform, to allow separate
+ * generateMutation() and applyMutation() phases, allowing serialization.
+ */
+ public static class AssociatedMutation extends Mutation {
+ public int insnToDuplicateIdx;
+
+ @Override
+ public String getString() {
+ return Integer.toString(insnToDuplicateIdx);
+ }
+
+ @Override
+ public void parseString(String[] elements) {
+ insnToDuplicateIdx = Integer.parseInt(elements[2]);
+ }
+ }
+
+ // The following two methods are here for the benefit of MutationSerializer,
+ // so it can create a CodeMutator and get the correct associated Mutation, as it
+ // reads in mutations from a dump of mutations.
+ @Override
+ public Mutation getNewMutation() {
+ return new AssociatedMutation();
+ }
+
+ public InstructionDuplicator() { }
+
+ public InstructionDuplicator(Random rng, MutationStats stats, List<Mutation> mutations) {
+ super(rng, stats, mutations);
+ likelihood = 80;
+ }
+
+ @Override
+ protected Mutation generateMutation(MutatableCode mutatableCode) {
+ boolean foundInsn = false;
+ int insnIdx = 0;
+
+ while (!foundInsn) {
+ // Pick an instruction at random...
+ insnIdx = rng.nextInt(mutatableCode.getInstructionCount());
+ MInsn oldInsn = mutatableCode.getInstructionAt(insnIdx);
+ foundInsn = true;
+ Opcode opcode = oldInsn.insn.info.opcode;
+ // ...check it's a legal instruction to duplicate.
+ if (opcode == Opcode.SPARSE_SWITCH || opcode == Opcode.PACKED_SWITCH
+ || opcode == Opcode.FILL_ARRAY_DATA || oldInsn.insn.justRaw) {
+ foundInsn = false;
+ }
+ }
+
+ AssociatedMutation mutation = new AssociatedMutation();
+ mutation.setup(this.getClass(), mutatableCode);
+ mutation.insnToDuplicateIdx = insnIdx;
+ return mutation;
+ }
+
+ @Override
+ protected void applyMutation(Mutation uncastMutation) {
+ // Cast the Mutation to our AssociatedMutation, so we can access its fields.
+ AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
+ MutatableCode mutatableCode = mutation.mutatableCode;
+
+ MInsn oldInsn = mutatableCode.getInstructionAt(mutation.insnToDuplicateIdx);
+
+ MInsn newInsn = oldInsn.clone();
+
+ Log.info("Duplicating " + oldInsn);
+
+ stats.incrementStat("Duplicated instruction");
+
+ mutatableCode.insertInstructionAt(newInsn, mutation.insnToDuplicateIdx);
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/InstructionSwapper.java b/tools/dexfuzz/src/dexfuzz/program/mutators/InstructionSwapper.java
new file mode 100644
index 0000000..17ea939
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/InstructionSwapper.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.program.MInsn;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+
+import java.util.List;
+import java.util.Random;
+
+public class InstructionSwapper extends CodeMutator {
+ /**
+ * Every CodeMutator has an AssociatedMutation, representing the
+ * mutation that this CodeMutator can perform, to allow separate
+ * generateMutation() and applyMutation() phases, allowing serialization.
+ */
+ public static class AssociatedMutation extends Mutation {
+ public int swapInsnIdx;
+ public int swapWithInsnIdx;
+
+ @Override
+ public String getString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(swapInsnIdx).append(" ");
+ builder.append(swapWithInsnIdx);
+ return builder.toString();
+ }
+
+ @Override
+ public void parseString(String[] elements) {
+ swapInsnIdx = Integer.parseInt(elements[2]);
+ swapWithInsnIdx = Integer.parseInt(elements[3]);
+ }
+ }
+
+ // The following two methods are here for the benefit of MutationSerializer,
+ // so it can create a CodeMutator and get the correct associated Mutation, as it
+ // reads in mutations from a dump of mutations.
+ @Override
+ public Mutation getNewMutation() {
+ return new AssociatedMutation();
+ }
+
+ public InstructionSwapper() { }
+
+ public InstructionSwapper(Random rng, MutationStats stats, List<Mutation> mutations) {
+ super(rng, stats, mutations);
+ likelihood = 80;
+ }
+
+ @Override
+ protected boolean canMutate(MutatableCode mutatableCode) {
+ if (mutatableCode.getInstructionCount() == 1) {
+ // Cannot swap one instruction.
+ Log.debug("Cannot swap insns in a method with only one.");
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected Mutation generateMutation(MutatableCode mutatableCode) {
+ int swapInsnIdx = 0;
+ int swapWithInsnIdx = 0;
+
+ boolean foundFirstInsn = false;
+ boolean foundSecondInsn = false;
+
+ while (!foundFirstInsn || !foundSecondInsn) {
+ // Look for the first insn.
+ while (!foundFirstInsn) {
+ swapInsnIdx = rng.nextInt(mutatableCode.getInstructionCount());
+ MInsn toBeSwapped = mutatableCode.getInstructionAt(swapInsnIdx);
+ foundFirstInsn = true;
+ if (toBeSwapped.insn.justRaw) {
+ foundFirstInsn = false;
+ }
+ }
+
+ // Look for the second insn.
+ int secondInsnAttempts = 0;
+ while (!foundSecondInsn) {
+ int delta = rng.nextInt(5) - 1;
+
+ if (delta == 0) {
+ continue;
+ }
+
+ swapWithInsnIdx = swapInsnIdx + delta;
+ foundSecondInsn = true;
+
+ // Check insn is in valid range.
+ if (swapWithInsnIdx < 0) {
+ foundSecondInsn = false;
+ } else if (swapWithInsnIdx >= mutatableCode.getInstructionCount()) {
+ foundSecondInsn = false;
+ }
+
+ // Finally, check if we're swapping with a raw insn.
+ if (foundSecondInsn) {
+ if (mutatableCode.getInstructionAt(swapWithInsnIdx).insn.justRaw) {
+ foundSecondInsn = false;
+ }
+ }
+
+ // If we've checked 10 times for an insn to swap with,
+ // and still found nothing, then try a new first insn.
+ if (!foundSecondInsn) {
+ secondInsnAttempts++;
+ if (secondInsnAttempts == 10) {
+ foundFirstInsn = false;
+ break;
+ }
+ }
+ }
+ }
+
+ AssociatedMutation mutation = new AssociatedMutation();
+ mutation.setup(this.getClass(), mutatableCode);
+ mutation.swapInsnIdx = swapInsnIdx;
+ mutation.swapWithInsnIdx = swapWithInsnIdx;
+ return mutation;
+ }
+
+ @Override
+ protected void applyMutation(Mutation uncastMutation) {
+ // Cast the Mutation to our AssociatedMutation, so we can access its fields.
+ AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
+ MutatableCode mutatableCode = mutation.mutatableCode;
+
+ MInsn toBeSwapped = mutatableCode.getInstructionAt(mutation.swapInsnIdx);
+ MInsn swappedWith = mutatableCode.getInstructionAt(mutation.swapWithInsnIdx);
+
+ Log.info("Swapping " + toBeSwapped + " with " + swappedWith);
+
+ stats.incrementStat("Swapped two instructions");
+
+ mutatableCode.swapInstructionsByIndex(mutation.swapInsnIdx, mutation.swapWithInsnIdx);
+
+ Log.info("Now " + swappedWith + " is swapped with " + toBeSwapped);
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/NewMethodCaller.java b/tools/dexfuzz/src/dexfuzz/program/mutators/NewMethodCaller.java
new file mode 100644
index 0000000..88a2f9a
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/NewMethodCaller.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.program.MInsn;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+import dexfuzz.rawdex.Instruction;
+import dexfuzz.rawdex.Instruction.InvokeFormatInfo;
+import dexfuzz.rawdex.Opcode;
+
+import java.util.List;
+import java.util.Random;
+
+public class NewMethodCaller extends CodeMutator {
+ /**
+ * Every CodeMutator has an AssociatedMutation, representing the
+ * mutation that this CodeMutator can perform, to allow separate
+ * generateMutation() and applyMutation() phases, allowing serialization.
+ */
+ public static class AssociatedMutation extends Mutation {
+ public enum InvokeType {
+ VIRTUAL,
+ DIRECT,
+ SUPER,
+ STATIC,
+ INTERFACE
+ }
+
+ public int insertionIdx;
+ public InvokeType invokeType;
+ public String className;
+ public String methodName;
+ public String signature;
+ public int numArgs;
+ public int[] args;
+
+ @Override
+ public String getString() {
+ StringBuilder argsString = new StringBuilder();
+ for (int i = 0; i < numArgs; i++) {
+ argsString.append(args[i]);
+ if (i < (numArgs - 1)) {
+ argsString.append(" ");
+ }
+ }
+ String result = String.format("%d %d %s %s %s %d %s",
+ insertionIdx,
+ invokeType.ordinal(),
+ className,
+ methodName,
+ signature,
+ numArgs,
+ argsString);
+ return result;
+ }
+
+ @Override
+ public void parseString(String[] elements) {
+ insertionIdx = Integer.parseInt(elements[2]);
+ invokeType = InvokeType.values()[Integer.parseInt(elements[3])];
+ className = elements[4];
+ methodName = elements[5];
+ signature = elements[6];
+ numArgs = Integer.parseInt(elements[7]);
+ args = new int[numArgs];
+ for (int i = 0; i < numArgs; i++) {
+ args[i] = Integer.parseInt(elements[8 + i]);
+ }
+ }
+ }
+
+ // The following two methods are here for the benefit of MutationSerializer,
+ // so it can create a CodeMutator and get the correct associated Mutation, as it
+ // reads in mutations from a dump of mutations.
+ @Override
+ public Mutation getNewMutation() {
+ return new AssociatedMutation();
+ }
+
+ public NewMethodCaller() { }
+
+ public NewMethodCaller(Random rng, MutationStats stats, List<Mutation> mutations) {
+ super(rng, stats, mutations);
+ likelihood = 10;
+ }
+
+ @Override
+ protected Mutation generateMutation(MutatableCode mutatableCode) {
+ // Find the insertion point.
+ int insertionIdx = 0;
+ boolean foundInsn = false;
+
+ while (!foundInsn) {
+ insertionIdx = rng.nextInt(mutatableCode.getInstructionCount());
+ MInsn insertionPoint =
+ mutatableCode.getInstructionAt(insertionIdx);
+ foundInsn = true;
+
+ // Don't want to insert instructions where there are raw instructions for now.
+ if (insertionPoint.insn.justRaw) {
+ foundInsn = false;
+ }
+ }
+
+ AssociatedMutation mutation = new AssociatedMutation();
+ mutation.setup(this.getClass(), mutatableCode);
+ mutation.insertionIdx = insertionIdx;
+
+ // TODO: Right now this mutator can only insert calls to System.gc() Add more!
+
+ mutation.invokeType = AssociatedMutation.InvokeType.STATIC;
+ mutation.className = "Ljava/lang/System;";
+ mutation.methodName = "gc";
+ mutation.signature = "()V";
+ mutation.numArgs = 0;
+
+ return mutation;
+ }
+
+ @Override
+ protected void applyMutation(Mutation uncastMutation) {
+ // Cast the Mutation to our AssociatedMutation, so we can access its fields.
+ AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
+ MutatableCode mutatableCode = mutation.mutatableCode;
+
+ MInsn newInsn = new MInsn();
+ newInsn.insn = new Instruction();
+
+ switch (mutation.invokeType) {
+ case VIRTUAL:
+ newInsn.insn.info = Instruction.getOpcodeInfo(Opcode.INVOKE_VIRTUAL);
+ break;
+ case DIRECT:
+ newInsn.insn.info = Instruction.getOpcodeInfo(Opcode.INVOKE_DIRECT);
+ break;
+ case SUPER:
+ newInsn.insn.info = Instruction.getOpcodeInfo(Opcode.INVOKE_SUPER);
+ break;
+ case STATIC:
+ newInsn.insn.info = Instruction.getOpcodeInfo(Opcode.INVOKE_STATIC);
+ break;
+ case INTERFACE:
+ newInsn.insn.info = Instruction.getOpcodeInfo(Opcode.INVOKE_INTERFACE);
+ break;
+ default:
+ }
+
+ // TODO: Handle more than just static invokes.
+
+ int methodIdx = mutatableCode.program.getNewItemCreator()
+ .findOrCreateMethodId(mutation.className,
+ mutation.methodName, mutation.signature);
+
+ newInsn.insn.vregB = methodIdx;
+ newInsn.insn.invokeFormatInfo = new InvokeFormatInfo();
+
+ // TODO: More field population, when we call methods that take arguments.
+
+ MInsn insertionPoint =
+ mutatableCode.getInstructionAt(mutation.insertionIdx);
+
+ Log.info(String.format("Called new method %s %s %s, inserting at %s",
+ mutation.className, mutation.methodName, mutation.signature, insertionPoint));
+
+ stats.incrementStat("Called new method");
+
+ mutatableCode.insertInstructionAt(newInsn, mutation.insertionIdx);
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/NonsenseStringPrinter.java b/tools/dexfuzz/src/dexfuzz/program/mutators/NonsenseStringPrinter.java
new file mode 100644
index 0000000..b4c9d7b
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/NonsenseStringPrinter.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.program.MInsn;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+import dexfuzz.rawdex.Instruction;
+import dexfuzz.rawdex.Opcode;
+
+import java.util.List;
+import java.util.Random;
+
+public class NonsenseStringPrinter extends CodeMutator {
+ /**
+ * Every CodeMutator has an AssociatedMutation, representing the
+ * mutation that this CodeMutator can perform, to allow separate
+ * generateMutation() and applyMutation() phases, allowing serialization.
+ */
+ public static class AssociatedMutation extends Mutation {
+ public int insertionIdx;
+ public String nonsenseString;
+
+ @Override
+ public String getString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(insertionIdx).append(" ");
+ builder.append(nonsenseString);
+ return builder.toString();
+ }
+
+ @Override
+ public void parseString(String[] elements) {
+ insertionIdx = Integer.parseInt(elements[2]);
+ nonsenseString = elements[3];
+ }
+ }
+
+ // The following two methods are here for the benefit of MutationSerializer,
+ // so it can create a CodeMutator and get the correct associated Mutation, as it
+ // reads in mutations from a dump of mutations.
+ @Override
+ public Mutation getNewMutation() {
+ return new AssociatedMutation();
+ }
+
+ public NonsenseStringPrinter() { }
+
+ public NonsenseStringPrinter(Random rng, MutationStats stats, List<Mutation> mutations) {
+ super(rng, stats, mutations);
+ likelihood = 10;
+ }
+
+ @Override
+ protected Mutation generateMutation(MutatableCode mutatableCode) {
+ // Find the insertion point
+ int insertionIdx = 0;
+ boolean foundInsn = false;
+
+ while (!foundInsn) {
+ insertionIdx = rng.nextInt(mutatableCode.getInstructionCount());
+ MInsn insertionPoint =
+ mutatableCode.getInstructionAt(insertionIdx);
+ foundInsn = true;
+
+ // Don't want to insert instructions where there are raw instructions for now.
+ if (insertionPoint.insn.justRaw) {
+ foundInsn = false;
+ }
+ }
+
+ AssociatedMutation mutation = new AssociatedMutation();
+ mutation.setup(this.getClass(), mutatableCode);
+ mutation.insertionIdx = insertionIdx;
+ mutation.nonsenseString = getRandomString();
+ return mutation;
+ }
+
+ @Override
+ protected void applyMutation(Mutation uncastMutation) {
+ // Cast the Mutation to our AssociatedMutation, so we can access its fields.
+ AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
+ MutatableCode mutatableCode = mutation.mutatableCode;
+
+ int outFieldIdx = mutatableCode.program.getNewItemCreator().findOrCreateFieldId(
+ "Ljava/lang/System;",
+ "Ljava/io/PrintStream;",
+ "out");
+ int printMethodIdx = mutatableCode.program.getNewItemCreator().findOrCreateMethodId(
+ "Ljava/io/PrintStream;",
+ "print",
+ "(Ljava/lang/String;)V");
+ int nonsenseStringIdx = mutatableCode.program.getNewItemCreator().findOrCreateString(
+ mutation.nonsenseString);
+
+ MInsn insertionPoint = mutatableCode.getInstructionAt(mutation.insertionIdx);
+
+ mutatableCode.allocateTemporaryVRegs(2);
+
+ int streamRegister = mutatableCode.getTemporaryVReg(0);
+ int stringRegister = mutatableCode.getTemporaryVReg(1);
+
+ // Load into string and stream into the temporary registers.
+ // then call print(stream, string)
+ MInsn constStringInsn = new MInsn();
+ constStringInsn.insn = new Instruction();
+ constStringInsn.insn.info = Instruction.getOpcodeInfo(Opcode.CONST_STRING);
+ constStringInsn.insn.vregB = nonsenseStringIdx;
+ constStringInsn.insn.vregA = stringRegister;
+
+ MInsn streamLoadInsn = new MInsn();
+ streamLoadInsn.insn = new Instruction();
+ streamLoadInsn.insn.info = Instruction.getOpcodeInfo(Opcode.SGET_OBJECT);
+ streamLoadInsn.insn.vregB = outFieldIdx;
+ streamLoadInsn.insn.vregA = streamRegister;
+
+ MInsn invokeInsn = new MInsn();
+ invokeInsn.insn = new Instruction();
+ invokeInsn.insn.info = Instruction.getOpcodeInfo(Opcode.INVOKE_VIRTUAL_RANGE);
+ invokeInsn.insn.vregA = 2;
+ invokeInsn.insn.vregB = printMethodIdx;
+ invokeInsn.insn.vregC = streamRegister;
+
+ Log.info(String.format("Printing nonsense string '%s', inserting at %s",
+ mutation.nonsenseString, insertionPoint));
+
+ stats.incrementStat("Printed nonsense string");
+
+ mutatableCode.insertInstructionAt(invokeInsn, mutation.insertionIdx);
+ mutatableCode.insertInstructionAt(streamLoadInsn, mutation.insertionIdx);
+ mutatableCode.insertInstructionAt(constStringInsn, mutation.insertionIdx);
+
+ mutatableCode.finishedUsingTemporaryVRegs();
+ }
+
+ private String getRandomString() {
+ int size = rng.nextInt(10);
+ int start = (int) 'A';
+ int end = (int) 'Z';
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < size; i++) {
+ builder.append((char) (rng.nextInt((end + 1) - start) + start));
+ }
+ return builder.toString();
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/PoolIndexChanger.java b/tools/dexfuzz/src/dexfuzz/program/mutators/PoolIndexChanger.java
new file mode 100644
index 0000000..cae5dc1
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/PoolIndexChanger.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.program.MInsn;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+import dexfuzz.rawdex.formats.ContainsPoolIndex;
+import dexfuzz.rawdex.formats.ContainsPoolIndex.PoolIndexKind;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class PoolIndexChanger extends CodeMutator {
+ /**
+ * Every CodeMutator has an AssociatedMutation, representing the
+ * mutation that this CodeMutator can perform, to allow separate
+ * generateMutation() and applyMutation() phases, allowing serialization.
+ */
+ public static class AssociatedMutation extends Mutation {
+ public int poolIndexInsnIdx;
+ public int newPoolIndex;
+
+ @Override
+ public String getString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(poolIndexInsnIdx).append(" ");
+ builder.append(newPoolIndex);
+ return builder.toString();
+ }
+
+ @Override
+ public void parseString(String[] elements) {
+ poolIndexInsnIdx = Integer.parseInt(elements[2]);
+ newPoolIndex = Integer.parseInt(elements[3]);
+ }
+ }
+
+ // The following two methods are here for the benefit of MutationSerializer,
+ // so it can create a CodeMutator and get the correct associated Mutation, as it
+ // reads in mutations from a dump of mutations.
+ @Override
+ public Mutation getNewMutation() {
+ return new AssociatedMutation();
+ }
+
+ public PoolIndexChanger() { }
+
+ public PoolIndexChanger(Random rng, MutationStats stats, List<Mutation> mutations) {
+ super(rng, stats, mutations);
+ likelihood = 30;
+ }
+
+ // A cache that should only exist between generateMutation() and applyMutation(),
+ // or be created at the start of applyMutation(), if we're reading in mutations from
+ // a file.
+ private List<MInsn> poolIndexInsns = null;
+
+ private void generateCachedPoolIndexInsns(MutatableCode mutatableCode) {
+ if (poolIndexInsns != null) {
+ return;
+ }
+
+ poolIndexInsns = new ArrayList<MInsn>();
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (mInsn.insn.info.format instanceof ContainsPoolIndex) {
+ poolIndexInsns.add(mInsn);
+ }
+ }
+ }
+
+ @Override
+ protected boolean canMutate(MutatableCode mutatableCode) {
+ // Remember what kinds of pool indices we see.
+ List<PoolIndexKind> seenKinds = new ArrayList<PoolIndexKind>();
+
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (mInsn.insn.info.format instanceof ContainsPoolIndex) {
+
+ ContainsPoolIndex containsPoolIndex =
+ (ContainsPoolIndex)mInsn.insn.info.format;
+
+ PoolIndexKind newPoolIndexKind =
+ containsPoolIndex.getPoolIndexKind(mInsn.insn.info);
+
+ seenKinds.add(newPoolIndexKind);
+ }
+ }
+
+ // Now check that there exists a kind such that the max pool index for
+ // the kind is greater than 1 (i.e., something can be changed)
+ if (!seenKinds.isEmpty()) {
+
+ for (PoolIndexKind kind : seenKinds) {
+ int numPoolIndices = mutatableCode.program.getTotalPoolIndicesByKind(kind);
+ if (numPoolIndices > 1) {
+ return true;
+ }
+ }
+
+ Log.debug("Method does not contain any insns that index into a const pool size > 1");
+ return false;
+ }
+
+ Log.debug("Method contains no instructions that index into the constant pool.");
+ return false;
+ }
+
+ @Override
+ protected Mutation generateMutation(MutatableCode mutatableCode) {
+ generateCachedPoolIndexInsns(mutatableCode);
+
+ int poolIndexInsnIdx = 0;
+ boolean found = false;
+
+ int oldPoolIndex = 0;
+ int newPoolIndex = 0;
+ int maxPoolIndex = 0;
+
+ // Pick a random instruction.
+ while (!found) {
+ poolIndexInsnIdx = rng.nextInt(poolIndexInsns.size());
+ MInsn poolIndexInsn = poolIndexInsns.get(poolIndexInsnIdx);
+
+ found = true;
+
+ ContainsPoolIndex containsPoolIndex =
+ (ContainsPoolIndex)poolIndexInsn.insn.info.format;
+
+ // Get the pool index.
+ oldPoolIndex = containsPoolIndex.getPoolIndex(poolIndexInsn.insn);
+ newPoolIndex = oldPoolIndex;
+
+ // Get the largest pool index available for the provided kind of pool index.
+ PoolIndexKind poolIndexKind =
+ containsPoolIndex.getPoolIndexKind(poolIndexInsn.insn.info);
+ maxPoolIndex = mutatableCode.program.getTotalPoolIndicesByKind(poolIndexKind);
+
+ if (maxPoolIndex <= 1) {
+ found = false;
+ }
+ }
+
+ // Get a new pool index.
+ while (newPoolIndex == oldPoolIndex) {
+ newPoolIndex = rng.nextInt(maxPoolIndex);
+ }
+
+ AssociatedMutation mutation = new AssociatedMutation();
+ mutation.setup(this.getClass(), mutatableCode);
+ mutation.poolIndexInsnIdx = poolIndexInsnIdx;
+ mutation.newPoolIndex = newPoolIndex;
+ return mutation;
+ }
+
+ @Override
+ protected void applyMutation(Mutation uncastMutation) {
+ // Cast the Mutation to our AssociatedMutation, so we can access its fields.
+ AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
+ MutatableCode mutatableCode = mutation.mutatableCode;
+
+ generateCachedPoolIndexInsns(mutatableCode);
+
+ MInsn poolIndexInsn = poolIndexInsns.get(mutation.poolIndexInsnIdx);
+
+ ContainsPoolIndex containsPoolIndex =
+ (ContainsPoolIndex) poolIndexInsn.insn.info.format;
+
+ int oldPoolIndex = containsPoolIndex.getPoolIndex(poolIndexInsn.insn);
+
+ Log.info("Changed pool index " + oldPoolIndex + " to " + mutation.newPoolIndex
+ + " in " + poolIndexInsn);
+
+ stats.incrementStat("Changed constant pool index");
+
+ // Set the new pool index.
+ containsPoolIndex.setPoolIndex(poolIndexInsn.insn, mutation.newPoolIndex);
+
+ // Clear cache.
+ poolIndexInsns = null;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/RandomInstructionGenerator.java b/tools/dexfuzz/src/dexfuzz/program/mutators/RandomInstructionGenerator.java
new file mode 100644
index 0000000..ff43c7c
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/RandomInstructionGenerator.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.program.MBranchInsn;
+import dexfuzz.program.MInsn;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+import dexfuzz.rawdex.Instruction;
+import dexfuzz.rawdex.Opcode;
+import dexfuzz.rawdex.OpcodeInfo;
+import dexfuzz.rawdex.formats.AbstractFormat;
+import dexfuzz.rawdex.formats.ContainsConst;
+import dexfuzz.rawdex.formats.ContainsPoolIndex;
+import dexfuzz.rawdex.formats.ContainsPoolIndex.PoolIndexKind;
+import dexfuzz.rawdex.formats.ContainsVRegs;
+
+import java.util.List;
+import java.util.Random;
+
+public class RandomInstructionGenerator extends CodeMutator {
+ /**
+ * Every CodeMutator has an AssociatedMutation, representing the
+ * mutation that this CodeMutator can perform, to allow separate
+ * generateMutation() and applyMutation() phases, allowing serialization.
+ */
+ public static class AssociatedMutation extends Mutation {
+ public int insertionIdx;
+ public int newOpcode;
+ public boolean hasConst;
+ public long constValue;
+ public boolean hasPoolIndex;
+ public int poolIndexValue;
+ public boolean hasVregs;
+ public int vregCount;
+ public int vregA;
+ public int vregB;
+ public int vregC;
+ public int branchTargetIdx;
+
+ @Override
+ public String getString() {
+ String result = String.format("%d %d %s %d %s %d %s %d %d %d %d %d",
+ insertionIdx,
+ newOpcode,
+ (hasConst ? "T" : "F"),
+ constValue,
+ (hasPoolIndex ? "T" : "F"),
+ poolIndexValue,
+ (hasVregs ? "T" : "F"),
+ vregCount,
+ vregA,
+ vregB,
+ vregC,
+ branchTargetIdx
+ );
+ return result;
+ }
+
+ @Override
+ public void parseString(String[] elements) {
+ insertionIdx = Integer.parseInt(elements[2]);
+ newOpcode = Integer.parseInt(elements[3]);
+ hasConst = (elements[4].equals("T"));
+ constValue = Long.parseLong(elements[5]);
+ hasPoolIndex = (elements[6].equals("T"));
+ poolIndexValue = Integer.parseInt(elements[7]);
+ hasVregs = (elements[8].equals("T"));
+ vregCount = Integer.parseInt(elements[9]);
+ vregA = Integer.parseInt(elements[10]);
+ vregB = Integer.parseInt(elements[11]);
+ vregC = Integer.parseInt(elements[12]);
+ branchTargetIdx = Integer.parseInt(elements[13]);
+ }
+ }
+
+ // The following two methods are here for the benefit of MutationSerializer,
+ // so it can create a CodeMutator and get the correct associated Mutation, as it
+ // reads in mutations from a dump of mutations.
+ @Override
+ public Mutation getNewMutation() {
+ return new AssociatedMutation();
+ }
+
+ public RandomInstructionGenerator() { }
+
+ public RandomInstructionGenerator(Random rng, MutationStats stats, List<Mutation> mutations) {
+ super(rng, stats, mutations);
+ likelihood = 30;
+ }
+
+ @Override
+ protected Mutation generateMutation(MutatableCode mutatableCode) {
+ // Find the insertion point.
+ int insertionIdx = 0;
+ boolean foundInsn = false;
+
+ while (!foundInsn) {
+ insertionIdx = rng.nextInt(mutatableCode.getInstructionCount());
+ MInsn insertionPoint =
+ mutatableCode.getInstructionAt(insertionIdx);
+ foundInsn = true;
+
+ // Don't want to insert instructions where there are raw instructions for now.
+ if (insertionPoint.insn.justRaw) {
+ foundInsn = false;
+ }
+ }
+
+ Opcode newOpcode = null;
+ int opcodeCount = Opcode.values().length;
+ boolean foundOpcode = false;
+
+ while (!foundOpcode) {
+ newOpcode = Opcode.values()[rng.nextInt(opcodeCount)];
+ foundOpcode = true;
+ if (Opcode.isBetween(newOpcode, Opcode.FILLED_NEW_ARRAY, Opcode.FILL_ARRAY_DATA)
+ || Opcode.isBetween(newOpcode, Opcode.PACKED_SWITCH, Opcode.SPARSE_SWITCH)
+ || Opcode.isBetween(newOpcode, Opcode.INVOKE_VIRTUAL, Opcode.INVOKE_INTERFACE)
+ || Opcode.isBetween(newOpcode,
+ Opcode.INVOKE_VIRTUAL_RANGE, Opcode.INVOKE_INTERFACE_RANGE)
+ // Can never accept these instructions at compile time.
+ || Opcode.isBetween(newOpcode, Opcode.IGET_QUICK, Opcode.IPUT_SHORT_QUICK)
+ // Unused opcodes...
+ || Opcode.isBetween(newOpcode, Opcode.UNUSED_3E, Opcode.UNUSED_43)
+ || Opcode.isBetween(newOpcode, Opcode.UNUSED_79, Opcode.UNUSED_7A)
+ || Opcode.isBetween(newOpcode, Opcode.UNUSED_EF, Opcode.UNUSED_FF)) {
+ foundOpcode = false;
+ }
+ }
+
+ OpcodeInfo newOpcodeInfo = Instruction.getOpcodeInfo(newOpcode);
+
+ AssociatedMutation mutation = new AssociatedMutation();
+ mutation.setup(this.getClass(), mutatableCode);
+ mutation.insertionIdx = insertionIdx;
+ mutation.newOpcode = newOpcodeInfo.value;
+
+ AbstractFormat fmt = newOpcodeInfo.format;
+
+ if (fmt instanceof ContainsConst) {
+ mutation.hasConst = true;
+ mutation.constValue = rng.nextLong() % ((ContainsConst)fmt).getConstRange();
+ }
+ if (fmt instanceof ContainsPoolIndex) {
+ mutation.hasPoolIndex = true;
+ ContainsPoolIndex containsPoolIndex = (ContainsPoolIndex) fmt;
+ PoolIndexKind poolIndexKind = containsPoolIndex.getPoolIndexKind(newOpcodeInfo);
+ int maxPoolIndex = mutatableCode.program.getTotalPoolIndicesByKind(poolIndexKind);
+ if (maxPoolIndex > 0) {
+ mutation.poolIndexValue = rng.nextInt(maxPoolIndex);
+ } else {
+ mutation.poolIndexValue = 0;
+ }
+ }
+ if (mutatableCode.registersSize == 0) {
+ mutatableCode.registersSize = 1;
+ }
+ if (fmt instanceof ContainsVRegs) {
+ mutation.hasVregs = true;
+ ContainsVRegs containsVregs = (ContainsVRegs) fmt;
+ mutation.vregCount = containsVregs.getVRegCount();
+ switch (mutation.vregCount) {
+ case 3:
+ mutation.vregC = rng.nextInt(mutatableCode.registersSize);
+ // fallthrough
+ case 2:
+ mutation.vregB = rng.nextInt(mutatableCode.registersSize);
+ // fallthrough
+ case 1:
+ mutation.vregA = rng.nextInt(mutatableCode.registersSize);
+ break;
+ default:
+ Log.errorAndQuit("Invalid number of vregs specified.");
+ }
+ }
+ // If we have some kind of branch, pick a random target.
+ if (Opcode.isBetween(newOpcode, Opcode.IF_EQ, Opcode.IF_LEZ)
+ || Opcode.isBetween(newOpcode, Opcode.GOTO, Opcode.GOTO_32)) {
+ mutation.branchTargetIdx = rng.nextInt(mutatableCode.getInstructionCount());
+ }
+
+ return mutation;
+ }
+
+ @Override
+ protected void applyMutation(Mutation uncastMutation) {
+ // Cast the Mutation to our AssociatedMutation, so we can access its fields.
+ AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
+ MutatableCode mutatableCode = mutation.mutatableCode;
+
+ Opcode newOpcode = Instruction.getOpcodeInfo(mutation.newOpcode).opcode;
+
+ boolean isBranch = false;
+ if (Opcode.isBetween(newOpcode, Opcode.IF_EQ, Opcode.IF_LEZ)
+ || Opcode.isBetween(newOpcode, Opcode.GOTO, Opcode.GOTO_32)) {
+ isBranch = true;
+ }
+
+ MInsn newInsn = null;
+ if (!isBranch) {
+ newInsn = new MInsn();
+ } else {
+ newInsn = new MBranchInsn();
+ }
+ newInsn.insn = new Instruction();
+ newInsn.insn.info = Instruction.getOpcodeInfo(mutation.newOpcode);
+ AbstractFormat fmt = newInsn.insn.info.format;
+
+ if (mutation.hasConst) {
+ ContainsConst containsConst = (ContainsConst) fmt;
+ containsConst.setConst(newInsn.insn, mutation.constValue);
+ }
+ if (mutation.hasPoolIndex) {
+ ContainsPoolIndex containsPoolIndex = (ContainsPoolIndex) fmt;
+ containsPoolIndex.setPoolIndex(newInsn.insn, mutation.poolIndexValue);
+ }
+ if (mutation.hasVregs) {
+ switch (mutation.vregCount) {
+ case 3:
+ newInsn.insn.vregC = mutation.vregC;
+ // fallthrough
+ case 2:
+ newInsn.insn.vregB = mutation.vregB;
+ // fallthrough
+ case 1:
+ newInsn.insn.vregA = mutation.vregA;
+ break;
+ default:
+ Log.errorAndQuit("Invalid number of vregs specified.");
+ }
+ }
+
+ if (isBranch) {
+ // We have a branch instruction, point it at its target.
+ MBranchInsn newBranchInsn = (MBranchInsn) newInsn;
+ newBranchInsn.target = mutatableCode.getInstructionAt(mutation.branchTargetIdx);
+ }
+
+ MInsn insertionPoint =
+ mutatableCode.getInstructionAt(mutation.insertionIdx);
+
+ Log.info("Generated random instruction: " + newInsn
+ + ", inserting at " + insertionPoint);
+
+ stats.incrementStat("Generated random instruction");
+
+ mutatableCode.insertInstructionAt(newInsn, mutation.insertionIdx);
+
+ // If we've generated a monitor insn, generate the matching opposing insn.
+ if (newInsn.insn.info.opcode == Opcode.MONITOR_ENTER) {
+ MInsn exitInsn = newInsn.clone();
+ exitInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MONITOR_EXIT);
+ mutatableCode.insertInstructionAfter(exitInsn, mutation.insertionIdx);
+ Log.info("Generated matching monitor-exit: " + exitInsn);
+ } else if (newInsn.insn.info.opcode == Opcode.MONITOR_EXIT) {
+ MInsn enterInsn = newInsn.clone();
+ enterInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MONITOR_ENTER);
+ mutatableCode.insertInstructionAt(enterInsn, mutation.insertionIdx);
+ Log.info("Generated matching monitor-enter: " + enterInsn);
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/SwitchBranchShifter.java b/tools/dexfuzz/src/dexfuzz/program/mutators/SwitchBranchShifter.java
new file mode 100644
index 0000000..6981034
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/SwitchBranchShifter.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.program.MInsn;
+import dexfuzz.program.MSwitchInsn;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class SwitchBranchShifter extends CodeMutator {
+ /**
+ * Every CodeMutator has an AssociatedMutation, representing the
+ * mutation that this CodeMutator can perform, to allow separate
+ * generateMutation() and applyMutation() phases, allowing serialization.
+ */
+ public static class AssociatedMutation extends Mutation {
+ public int switchInsnIdx;
+ public int switchTargetIdx;
+ public int newTargetIdx;
+
+ @Override
+ public String getString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(switchInsnIdx).append(" ");
+ builder.append(switchTargetIdx).append(" ");
+ builder.append(newTargetIdx);
+ return builder.toString();
+ }
+
+ @Override
+ public void parseString(String[] elements) {
+ switchInsnIdx = Integer.parseInt(elements[2]);
+ switchTargetIdx = Integer.parseInt(elements[3]);
+ newTargetIdx = Integer.parseInt(elements[4]);
+ }
+ }
+
+ // The following two methods are here for the benefit of MutationSerializer,
+ // so it can create a CodeMutator and get the correct associated Mutation, as it
+ // reads in mutations from a dump of mutations.
+ @Override
+ public Mutation getNewMutation() {
+ return new AssociatedMutation();
+ }
+
+ public SwitchBranchShifter() { }
+
+ public SwitchBranchShifter(Random rng, MutationStats stats, List<Mutation> mutations) {
+ super(rng, stats, mutations);
+ likelihood = 30;
+ }
+
+ // A cache that should only exist between generateMutation() and applyMutation(),
+ // or be created at the start of applyMutation(), if we're reading in mutations from
+ // a file.
+ private List<MSwitchInsn> switchInsns;
+
+ private void generateCachedSwitchInsns(MutatableCode mutatableCode) {
+ if (switchInsns != null) {
+ return;
+ }
+
+ switchInsns = new ArrayList<MSwitchInsn>();
+
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (mInsn instanceof MSwitchInsn) {
+ switchInsns.add((MSwitchInsn) mInsn);
+ }
+ }
+ }
+
+ @Override
+ protected boolean canMutate(MutatableCode mutatableCode) {
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (mInsn instanceof MSwitchInsn) {
+ return true;
+ }
+ }
+
+ Log.debug("Method contains no switch instructions.");
+ return false;
+ }
+
+ @Override
+ protected Mutation generateMutation(MutatableCode mutatableCode) {
+ generateCachedSwitchInsns(mutatableCode);
+
+ // Pick a random switch instruction.
+ int switchInsnIdx = rng.nextInt(switchInsns.size());
+ MSwitchInsn switchInsn = switchInsns.get(switchInsnIdx);
+
+ // Pick a random one of its targets.
+ int switchTargetIdx = rng.nextInt(switchInsn.targets.size());
+
+ // Get the original target, find its index.
+ MInsn oldTargetInsn = switchInsn.targets.get(switchTargetIdx);
+ int oldTargetInsnIdx = mutatableCode.getInstructionIndex(oldTargetInsn);
+
+ int newTargetIdx = oldTargetInsnIdx;
+
+ int delta = 0;
+
+ // Keep searching for a new index.
+ while (newTargetIdx == oldTargetInsnIdx) {
+ // Vary by +/- 2 instructions.
+ delta = 0;
+ while (delta == 0) {
+ delta = (rng.nextInt(5) - 2);
+ }
+
+ newTargetIdx = oldTargetInsnIdx + delta;
+
+ // Check the new index is legal
+ if (newTargetIdx < 0) {
+ newTargetIdx = 0;
+ } else if (newTargetIdx >= mutatableCode.getInstructionCount()) {
+ newTargetIdx = mutatableCode.getInstructionCount() - 1;
+ }
+ }
+
+ AssociatedMutation mutation = new AssociatedMutation();
+ mutation.setup(this.getClass(), mutatableCode);
+ mutation.switchInsnIdx = switchInsnIdx;
+ mutation.switchTargetIdx = switchTargetIdx;
+ mutation.newTargetIdx = newTargetIdx;
+ return mutation;
+ }
+
+ @Override
+ protected void applyMutation(Mutation uncastMutation) {
+ // Cast the Mutation to our AssociatedMutation, so we can access its fields.
+ AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
+ MutatableCode mutatableCode = mutation.mutatableCode;
+
+ generateCachedSwitchInsns(mutatableCode);
+
+ MSwitchInsn switchInsn = switchInsns.get(mutation.switchInsnIdx);
+
+ // Get the new target.
+ MInsn newTargetInsn =
+ mutatableCode.getInstructionAt(mutation.newTargetIdx);
+
+ // Set the new target.
+ switchInsn.targets.remove(mutation.switchTargetIdx);
+ switchInsn.targets.add(mutation.switchTargetIdx, newTargetInsn);
+
+ Log.info("Shifted target #" + mutation.switchTargetIdx + " of " + switchInsn
+ + " to point to " + newTargetInsn);
+
+ stats.incrementStat("Shifted switch target");
+
+ // Clear cache.
+ switchInsns = null;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/TryBlockShifter.java b/tools/dexfuzz/src/dexfuzz/program/mutators/TryBlockShifter.java
new file mode 100644
index 0000000..1bf6463
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/TryBlockShifter.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.program.MInsn;
+import dexfuzz.program.MTryBlock;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+
+import java.util.List;
+import java.util.Random;
+
+public class TryBlockShifter extends CodeMutator {
+ /**
+ * Every CodeMutator has an AssociatedMutation, representing the
+ * mutation that this CodeMutator can perform, to allow separate
+ * generateMutation() and applyMutation() phases, allowing serialization.
+ */
+ public static class AssociatedMutation extends Mutation {
+ public int tryIdx;
+ public boolean shiftingTryBlock; // false => shifting handler
+ public boolean shiftingStart; // false => shifting end (try block only)
+ public boolean shiftingHandlerCatchall;
+ public int shiftingHandlerIdx;
+ public int newShiftedInsnIdx;
+
+ @Override
+ public String getString() {
+ String result = String.format("%d %s %s %s %d %d",
+ tryIdx,
+ (shiftingTryBlock ? "T" : "F"),
+ (shiftingStart ? "T" : "F"),
+ (shiftingHandlerCatchall ? "T" : "F"),
+ shiftingHandlerIdx,
+ newShiftedInsnIdx
+ );
+ return result;
+ }
+
+ @Override
+ public void parseString(String[] elements) {
+ tryIdx = Integer.parseInt(elements[2]);
+ shiftingTryBlock = elements[3].equals("T");
+ shiftingStart = elements[4].equals("T");
+ shiftingHandlerCatchall = elements[5].equals("T");
+ shiftingHandlerIdx = Integer.parseInt(elements[6]);
+ newShiftedInsnIdx = Integer.parseInt(elements[7]);
+ }
+ }
+
+ // The following two methods are here for the benefit of MutationSerializer,
+ // so it can create a CodeMutator and get the correct associated Mutation, as it
+ // reads in mutations from a dump of mutations.
+ @Override
+ public Mutation getNewMutation() {
+ return new AssociatedMutation();
+ }
+
+ public TryBlockShifter() { }
+
+ public TryBlockShifter(Random rng, MutationStats stats, List<Mutation> mutations) {
+ super(rng, stats, mutations);
+ likelihood = 40;
+ }
+
+ @Override
+ protected boolean canMutate(MutatableCode mutatableCode) {
+ if (mutatableCode.triesSize > 0) {
+ return true;
+ }
+
+ Log.debug("Method contains no tries.");
+ return false;
+ }
+
+ @Override
+ protected Mutation generateMutation(MutatableCode mutatableCode) {
+ // Pick a random try.
+ int tryIdx = rng.nextInt(mutatableCode.triesSize);
+ MTryBlock tryBlock = mutatableCode.mutatableTries.get(tryIdx);
+
+ boolean shiftingTryBlock = rng.nextBoolean();
+ boolean shiftingStart = false;
+ boolean shiftingHandlerCatchall = false;
+ int shiftingHandlerIdx = -1;
+ if (shiftingTryBlock) {
+ // We're shifting the boundaries of the try block
+ // determine if we shift the start or the end.
+ shiftingStart = rng.nextBoolean();
+ } else {
+ // We're shifting the start of a handler of the try block.
+ if (tryBlock.handlers.isEmpty()) {
+ // No handlers, so we MUST mutate the catchall
+ shiftingHandlerCatchall = true;
+ } else if (tryBlock.catchAllHandler != null) {
+ // There is a catchall handler, so potentially mutate it.
+ shiftingHandlerCatchall = rng.nextBoolean();
+ }
+ // If we're not going to shift the catchall handler, then
+ // pick an explicit handler to shift.
+ if (!shiftingHandlerCatchall) {
+ shiftingHandlerIdx = rng.nextInt(tryBlock.handlers.size());
+ }
+ }
+
+ // Get the original instruction wherever we're shifting.
+ MInsn oldInsn = null;
+ if (shiftingTryBlock && shiftingStart) {
+ oldInsn = tryBlock.startInsn;
+ } else if (shiftingTryBlock && !(shiftingStart)) {
+ oldInsn = tryBlock.endInsn;
+ } else if (!(shiftingTryBlock) && shiftingHandlerCatchall) {
+ oldInsn = tryBlock.catchAllHandler;
+ } else if (!(shiftingTryBlock) && !(shiftingHandlerCatchall)
+ && (shiftingHandlerIdx != -1)) {
+ oldInsn = tryBlock.handlers.get(shiftingHandlerIdx);
+ } else {
+ Log.errorAndQuit("Faulty logic in TryBlockShifter!");
+ }
+
+ // Find the index of this instruction.
+ int oldInsnIdx = mutatableCode.getInstructionIndex(oldInsn);
+
+ int newInsnIdx = oldInsnIdx;
+
+ int delta = 0;
+
+ // Keep searching for a new index.
+ while (newInsnIdx == oldInsnIdx) {
+ // Vary by +/- 2 instructions.
+ delta = 0;
+ while (delta == 0) {
+ delta = (rng.nextInt(5) - 2);
+ }
+
+ newInsnIdx = oldInsnIdx + delta;
+
+ // Check the new index is legal.
+ if (newInsnIdx < 0) {
+ newInsnIdx = 0;
+ } else if (newInsnIdx >= mutatableCode.getInstructionCount()) {
+ newInsnIdx = mutatableCode.getInstructionCount() - 1;
+ }
+ }
+
+ AssociatedMutation mutation = new AssociatedMutation();
+ mutation.setup(this.getClass(), mutatableCode);
+ mutation.tryIdx = tryIdx;
+ mutation.shiftingTryBlock = shiftingTryBlock;
+ mutation.shiftingStart = shiftingStart;
+ mutation.shiftingHandlerCatchall = shiftingHandlerCatchall;
+ mutation.shiftingHandlerIdx = shiftingHandlerIdx;
+ mutation.newShiftedInsnIdx = newInsnIdx;
+ return mutation;
+ }
+
+ @Override
+ protected void applyMutation(Mutation uncastMutation) {
+ // Cast the Mutation to our AssociatedMutation, so we can access its fields.
+ AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
+ MutatableCode mutatableCode = mutation.mutatableCode;
+
+ MTryBlock tryBlock = mutatableCode.mutatableTries.get(mutation.tryIdx);
+
+ MInsn newInsn =
+ mutatableCode.getInstructionAt(mutation.newShiftedInsnIdx);
+
+ // Find the right mutatable instruction in try block, and point it at the new instruction.
+ if (mutation.shiftingTryBlock && mutation.shiftingStart) {
+ tryBlock.startInsn = newInsn;
+ Log.info("Shifted the start of try block #" + mutation.tryIdx
+ + " to be at " + newInsn);
+ } else if (mutation.shiftingTryBlock && !(mutation.shiftingStart)) {
+ tryBlock.endInsn = newInsn;
+ Log.info("Shifted the end of try block #" + mutation.tryIdx
+ + " to be at " + newInsn);
+ } else if (!(mutation.shiftingTryBlock) && mutation.shiftingHandlerCatchall) {
+ tryBlock.catchAllHandler = newInsn;
+ Log.info("Shifted the catch all handler of try block #" + mutation.tryIdx
+ + " to be at " + newInsn);
+ } else if (!(mutation.shiftingTryBlock) && !(mutation.shiftingHandlerCatchall)
+ && (mutation.shiftingHandlerIdx != -1)) {
+ tryBlock.handlers.set(mutation.shiftingHandlerIdx, newInsn);
+ Log.info("Shifted handler #" + mutation.shiftingHandlerIdx
+ + " of try block #" + mutation.tryIdx + " to be at " + newInsn);
+ } else {
+ Log.errorAndQuit("faulty logic in TryBlockShifter");
+ }
+
+ stats.incrementStat("Shifted boundary in a try block");
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/VRegChanger.java b/tools/dexfuzz/src/dexfuzz/program/mutators/VRegChanger.java
new file mode 100644
index 0000000..d685f7d
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/VRegChanger.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.program.MInsn;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+import dexfuzz.rawdex.formats.ContainsVRegs;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class VRegChanger extends CodeMutator {
+ /**
+ * Every CodeMutator has an AssociatedMutation, representing the
+ * mutation that this CodeMutator can perform, to allow separate
+ * generateMutation() and applyMutation() phases, allowing serialization.
+ */
+ public static class AssociatedMutation extends Mutation {
+ public int vregInsnIdx;
+ public int mutatingVreg;
+ public int newVregValue;
+
+ @Override
+ public String getString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(vregInsnIdx).append(" ");
+ builder.append(mutatingVreg).append(" ");
+ builder.append(newVregValue);
+ return builder.toString();
+ }
+
+ @Override
+ public void parseString(String[] elements) {
+ vregInsnIdx = Integer.parseInt(elements[2]);
+ mutatingVreg = Integer.parseInt(elements[3]);
+ newVregValue = Integer.parseInt(elements[4]);
+ }
+ }
+
+ // The following two methods are here for the benefit of MutationSerializer,
+ // so it can create a CodeMutator and get the correct associated Mutation, as it
+ // reads in mutations from a dump of mutations.
+ @Override
+ public Mutation getNewMutation() {
+ return new AssociatedMutation();
+ }
+
+ public VRegChanger() { }
+
+ public VRegChanger(Random rng, MutationStats stats, List<Mutation> mutations) {
+ super(rng, stats, mutations);
+ likelihood = 60;
+ }
+
+ // A cache that should only exist between generateMutation() and applyMutation(),
+ // or be created at the start of applyMutation(), if we're reading in mutations from
+ // a file.
+ private List<MInsn> vregInsns = null;
+
+ private void generateCachedVRegInsns(MutatableCode mutatableCode) {
+ if (vregInsns != null) {
+ return;
+ }
+
+ vregInsns = new ArrayList<MInsn>();
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (mInsn.insn.info.format instanceof ContainsVRegs) {
+ vregInsns.add(mInsn);
+ }
+ }
+ }
+
+ @Override
+ protected boolean canMutate(MutatableCode mutatableCode) {
+ if (mutatableCode.registersSize < 2) {
+ Log.debug("Impossible to change vregs in a method with fewer than 2 registers.");
+ return false;
+ }
+
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (mInsn.insn.info.format instanceof ContainsVRegs) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ protected Mutation generateMutation(MutatableCode mutatableCode) {
+ generateCachedVRegInsns(mutatableCode);
+
+ // Pick a random vreg instruction.
+ int vregInsnIdx = rng.nextInt(vregInsns.size());
+ MInsn vregInsn = vregInsns.get(vregInsnIdx);
+
+ // Get the number of VRegs this instruction uses.
+ int numVregs = ((ContainsVRegs)vregInsn.insn.info.format).getVRegCount();
+
+ // Pick which vreg to mutate.
+ int mutatingVreg = rng.nextInt(numVregs);
+
+ // Find the old index.
+ int oldVregValue = 0;
+
+ switch (mutatingVreg) {
+ case 0:
+ oldVregValue = (int) vregInsn.insn.vregA;
+ break;
+ case 1:
+ oldVregValue = (int) vregInsn.insn.vregB;
+ break;
+ case 2:
+ oldVregValue = (int) vregInsn.insn.vregC;
+ break;
+ default:
+ Log.errorAndQuit("Invalid number of vregs reported by a Format.");
+ }
+
+ // Search for a new vreg value.
+ int newVregValue = oldVregValue;
+ while (newVregValue == oldVregValue) {
+ newVregValue = rng.nextInt(mutatableCode.registersSize);
+ }
+
+ AssociatedMutation mutation = new AssociatedMutation();
+ mutation.setup(this.getClass(), mutatableCode);
+ mutation.vregInsnIdx = vregInsnIdx;
+ mutation.mutatingVreg = mutatingVreg;
+ mutation.newVregValue = newVregValue;
+ return mutation;
+ }
+
+ @Override
+ protected void applyMutation(Mutation uncastMutation) {
+ // Cast the Mutation to our AssociatedMutation, so we can access its fields.
+ AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
+ MutatableCode mutatableCode = mutation.mutatableCode;
+
+ generateCachedVRegInsns(mutatableCode);
+
+ MInsn vregInsn = vregInsns.get(mutation.vregInsnIdx);
+
+ // Remember what the instruction used to look like.
+ String oldInsnString = vregInsn.toString();
+
+ int oldVregValue = 0;
+
+ String vregId = "A";
+ switch (mutation.mutatingVreg) {
+ case 0:
+ oldVregValue = (int) vregInsn.insn.vregA;
+ vregInsn.insn.vregA = (long) mutation.newVregValue;
+ break;
+ case 1:
+ oldVregValue = (int) vregInsn.insn.vregB;
+ vregInsn.insn.vregB = (long) mutation.newVregValue;
+ vregId = "B";
+ break;
+ case 2:
+ oldVregValue = (int) vregInsn.insn.vregC;
+ vregInsn.insn.vregC = (long) mutation.newVregValue;
+ vregId = "C";
+ break;
+ default:
+ Log.errorAndQuit("Invalid number of vregs specified in a VRegChanger mutation.");
+ }
+
+ Log.info("In " + oldInsnString + " changed v" + vregId + ": v" + oldVregValue
+ + " to v" + mutation.newVregValue);
+
+ stats.incrementStat("Changed a virtual register");
+
+ // Clear cache.
+ vregInsns = null;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/ValuePrinter.java b/tools/dexfuzz/src/dexfuzz/program/mutators/ValuePrinter.java
new file mode 100644
index 0000000..271aca3
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/ValuePrinter.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.program.MInsn;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+import dexfuzz.rawdex.Instruction;
+import dexfuzz.rawdex.Opcode;
+
+import java.util.List;
+import java.util.Random;
+
+public class ValuePrinter extends CodeMutator {
+ /**
+ * Every CodeMutator has an AssociatedMutation, representing the
+ * mutation that this CodeMutator can perform, to allow separate
+ * generateMutation() and applyMutation() phases, allowing serialization.
+ */
+ public static class AssociatedMutation extends Mutation {
+ public int printedOutputIdx;
+
+ @Override
+ public String getString() {
+ return Integer.toString(printedOutputIdx);
+ }
+
+ @Override
+ public void parseString(String[] elements) {
+ printedOutputIdx = Integer.parseInt(elements[2]);
+ }
+ }
+
+ // The following two methods are here for the benefit of MutationSerializer,
+ // so it can create a CodeMutator and get the correct associated Mutation, as it
+ // reads in mutations from a dump of mutations.
+ @Override
+ public Mutation getNewMutation() {
+ return new AssociatedMutation();
+ }
+
+ public ValuePrinter() { }
+
+ public ValuePrinter(Random rng, MutationStats stats, List<Mutation> mutations) {
+ super(rng, stats, mutations);
+ likelihood = 40;
+ }
+
+ @Override
+ protected boolean canMutate(MutatableCode mutatableCode) {
+ for (MInsn mInsn : mutatableCode.getInstructions()) {
+ if (getInstructionOutputType(mInsn) != OutputType.UNKNOWN) {
+ return true;
+ }
+ }
+
+ Log.debug("No instructions with legible output in method, skipping.");
+ return false;
+ }
+
+ @Override
+ protected Mutation generateMutation(MutatableCode mutatableCode) {
+ // Find an instruction whose output we wish to print.
+ int printedOutputIdx = 0;
+ boolean foundInsn = false;
+
+ while (!foundInsn) {
+ printedOutputIdx = rng.nextInt(mutatableCode.getInstructionCount());
+ MInsn insnOutputToPrint =
+ mutatableCode.getInstructionAt(printedOutputIdx);
+ foundInsn = true;
+
+ // Don't want to insert instructions where there are raw instructions for now.
+ if (insnOutputToPrint.insn.justRaw) {
+ foundInsn = false;
+ }
+
+ if (getInstructionOutputType(insnOutputToPrint) == OutputType.UNKNOWN) {
+ foundInsn = false;
+ }
+ }
+
+ AssociatedMutation mutation = new AssociatedMutation();
+ mutation.setup(this.getClass(), mutatableCode);
+ mutation.printedOutputIdx = printedOutputIdx;
+ return mutation;
+ }
+
+ @Override
+ protected void applyMutation(Mutation uncastMutation) {
+ // Cast the Mutation to our AssociatedMutation, so we can access its fields.
+ AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
+ MutatableCode mutatableCode = mutation.mutatableCode;
+
+ MInsn insnOutputToPrint =
+ mutatableCode.getInstructionAt(mutation.printedOutputIdx);
+
+ int outFieldIdx = mutatableCode.program.getNewItemCreator().findOrCreateFieldId(
+ "Ljava/lang/System;",
+ "Ljava/io/PrintStream;",
+ "out");
+
+ OutputType outputType = getInstructionOutputType(insnOutputToPrint);
+
+ if (outputType == OutputType.UNKNOWN) {
+ Log.errorAndQuit("Requested to print output of an instruction, whose output"
+ + " type is unknown.");
+ }
+ int printMethodIdx = mutatableCode.program.getNewItemCreator().findOrCreateMethodId(
+ "Ljava/io/PrintStream;",
+ "print",
+ outputType.getSignatureForPrintln());
+
+ boolean isWide = false;
+ boolean isRef = false;
+ if (outputType == OutputType.LONG || outputType == OutputType.DOUBLE) {
+ isWide = true;
+ }
+ if (outputType == OutputType.STRING) {
+ isRef = true;
+ }
+
+ // If we're printing a wide value, we need to allocate 3 registers!
+ if (isWide) {
+ mutatableCode.allocateTemporaryVRegs(3);
+ } else {
+ mutatableCode.allocateTemporaryVRegs(2);
+ }
+
+ int streamRegister = mutatableCode.getTemporaryVReg(0);
+ int valueRegister = mutatableCode.getTemporaryVReg(1);
+
+ // Copy the value we want to print to the 2nd temporary register
+ // Then load the out stream
+ // Then call print(out stream, value)
+
+ MInsn valueCopyInsn = new MInsn();
+ valueCopyInsn.insn = new Instruction();
+ if (isRef) {
+ valueCopyInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_OBJECT_16);
+ } else if (isWide) {
+ valueCopyInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_WIDE_16);
+ } else {
+ valueCopyInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_16);
+ }
+ valueCopyInsn.insn.vregB = insnOutputToPrint.insn.vregA;
+ valueCopyInsn.insn.vregA = valueRegister;
+
+ MInsn streamLoadInsn = new MInsn();
+ streamLoadInsn.insn = new Instruction();
+ streamLoadInsn.insn.info = Instruction.getOpcodeInfo(Opcode.SGET_OBJECT);
+ streamLoadInsn.insn.vregB = outFieldIdx;
+ streamLoadInsn.insn.vregA = streamRegister;
+
+ MInsn invokeInsn = new MInsn();
+ invokeInsn.insn = new Instruction();
+ invokeInsn.insn.info = Instruction.getOpcodeInfo(Opcode.INVOKE_VIRTUAL_RANGE);
+ if (isWide) {
+ invokeInsn.insn.vregA = 3;
+ } else {
+ invokeInsn.insn.vregA = 2;
+ }
+ invokeInsn.insn.vregB = printMethodIdx;
+ invokeInsn.insn.vregC = streamRegister;
+
+ Log.info(String.format("Printing output value of instruction %s", insnOutputToPrint));
+
+ stats.incrementStat("Printed output value");
+
+ mutatableCode.insertInstructionAfter(invokeInsn, mutation.printedOutputIdx);
+ mutatableCode.insertInstructionAfter(streamLoadInsn, mutation.printedOutputIdx);
+ mutatableCode.insertInstructionAfter(valueCopyInsn, mutation.printedOutputIdx);
+
+ mutatableCode.finishedUsingTemporaryVRegs();
+ }
+
+ private static enum OutputType {
+ STRING("(Ljava/lang/String;)V"),
+ BOOLEAN("(Z)V"),
+ BYTE("(B)V"),
+ CHAR("(C)V"),
+ SHORT("(S)V"),
+ INT("(I)V"),
+ LONG("(J)V"),
+ FLOAT("(F)V"),
+ DOUBLE("(D)V"),
+ UNKNOWN("UNKNOWN");
+
+ private String printingSignature;
+ private OutputType(String s) {
+ printingSignature = s;
+ }
+
+ public String getSignatureForPrintln() {
+ return printingSignature;
+ }
+ }
+
+ private OutputType getInstructionOutputType(MInsn mInsn) {
+ Opcode opcode = mInsn.insn.info.opcode;
+ if (opcode == Opcode.CONST_STRING || opcode == Opcode.CONST_STRING_JUMBO) {
+ return OutputType.STRING;
+ }
+ if (opcode == Opcode.IGET_BOOLEAN || opcode == Opcode.SGET_BOOLEAN) {
+ return OutputType.BOOLEAN;
+ }
+ if (opcode == Opcode.IGET_BYTE || opcode == Opcode.SGET_BYTE
+ || opcode == Opcode.INT_TO_BYTE) {
+ return OutputType.BYTE;
+ }
+ if (opcode == Opcode.IGET_CHAR || opcode == Opcode.SGET_CHAR
+ || opcode == Opcode.INT_TO_CHAR) {
+ return OutputType.CHAR;
+ }
+ if (opcode == Opcode.IGET_SHORT || opcode == Opcode.SGET_SHORT
+ || opcode == Opcode.INT_TO_SHORT) {
+ return OutputType.SHORT;
+ }
+ if (opcode == Opcode.NEG_INT || opcode == Opcode.NOT_INT
+ || opcode == Opcode.LONG_TO_INT || opcode == Opcode.FLOAT_TO_INT
+ || opcode == Opcode.DOUBLE_TO_INT
+ || Opcode.isBetween(opcode, Opcode.ADD_INT, Opcode.USHR_INT)
+ || Opcode.isBetween(opcode, Opcode.ADD_INT_2ADDR, Opcode.USHR_INT_2ADDR)
+ || Opcode.isBetween(opcode, Opcode.ADD_INT_LIT16, Opcode.USHR_INT_LIT8)) {
+ return OutputType.INT;
+ }
+ if (opcode == Opcode.NEG_LONG || opcode == Opcode.NOT_LONG
+ || opcode == Opcode.INT_TO_LONG || opcode == Opcode.FLOAT_TO_LONG
+ || opcode == Opcode.DOUBLE_TO_LONG
+ || Opcode.isBetween(opcode, Opcode.ADD_LONG, Opcode.USHR_LONG)
+ || Opcode.isBetween(opcode, Opcode.ADD_LONG_2ADDR, Opcode.USHR_LONG_2ADDR)) {
+ return OutputType.LONG;
+ }
+ if (opcode == Opcode.NEG_FLOAT
+ || opcode == Opcode.INT_TO_FLOAT || opcode == Opcode.LONG_TO_FLOAT
+ || opcode == Opcode.DOUBLE_TO_FLOAT
+ || Opcode.isBetween(opcode, Opcode.ADD_FLOAT, Opcode.REM_FLOAT)
+ || Opcode.isBetween(opcode, Opcode.ADD_FLOAT_2ADDR, Opcode.REM_FLOAT_2ADDR)) {
+ return OutputType.FLOAT;
+ }
+ if (opcode == Opcode.NEG_DOUBLE
+ || opcode == Opcode.INT_TO_DOUBLE || opcode == Opcode.LONG_TO_DOUBLE
+ || opcode == Opcode.FLOAT_TO_DOUBLE
+ || Opcode.isBetween(opcode, Opcode.ADD_DOUBLE, Opcode.REM_DOUBLE)
+ || Opcode.isBetween(opcode, Opcode.ADD_DOUBLE_2ADDR, Opcode.REM_DOUBLE_2ADDR)) {
+ return OutputType.DOUBLE;
+ }
+ return OutputType.UNKNOWN;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationElement.java b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationElement.java
new file mode 100644
index 0000000..6bb2f96
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationElement.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class AnnotationElement implements RawDexObject {
+ public int nameIdx;
+ public EncodedValue value;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ nameIdx = file.readUleb128();
+ (value = new EncodedValue()).read(file);
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.writeUleb128(nameIdx);
+ value.write(file);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ if (kind == IndexUpdateKind.STRING_ID && nameIdx >= insertedIdx) {
+ nameIdx++;
+ }
+ value.incrementIndex(kind, insertedIdx);
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationItem.java
new file mode 100644
index 0000000..40cf5e4
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationItem.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class AnnotationItem implements RawDexObject {
+ public int visibility;
+ public EncodedAnnotation annotation;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().getNewOffsettable(file, this);
+ visibility = file.readUnsignedByte();
+ (annotation = new EncodedAnnotation()).read(file);
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().updatePositionOfNextOffsettable(file);
+ file.writeByte(visibility);
+ annotation.write(file);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ annotation.incrementIndex(kind, insertedIdx);
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationOffItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationOffItem.java
new file mode 100644
index 0000000..b44cd18
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationOffItem.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class AnnotationOffItem implements RawDexObject {
+ public Offset annotationOff;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ annotationOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().tryToWriteOffset(annotationOff, file, false /* ULEB128 */);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ // Do nothing.
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetItem.java
new file mode 100644
index 0000000..1e1c540
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetItem.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class AnnotationSetItem implements RawDexObject {
+ public int size;
+ public AnnotationOffItem[] entries;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ file.alignForwards(4);
+ file.getOffsetTracker().getNewOffsettable(file, this);
+ size = file.readUInt();
+ entries = new AnnotationOffItem[size];
+ for (int i = 0; i < size; i++) {
+ (entries[i] = new AnnotationOffItem()).read(file);
+ }
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.alignForwards(4);
+ file.getOffsetTracker().updatePositionOfNextOffsettable(file);
+ file.writeUInt(size);
+ for (AnnotationOffItem annotationOffItem : entries) {
+ annotationOffItem.write(file);
+ }
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ // Do nothing.
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetRefItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetRefItem.java
new file mode 100644
index 0000000..cb543de
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetRefItem.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class AnnotationSetRefItem implements RawDexObject {
+ public Offset annotationsOff;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ annotationsOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().tryToWriteOffset(annotationsOff, file, false /* ULEB128 */);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ // Do nothing.
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetRefList.java b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetRefList.java
new file mode 100644
index 0000000..1d27053
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationSetRefList.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class AnnotationSetRefList implements RawDexObject {
+ public int size;
+ public AnnotationSetRefItem[] list;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ file.alignForwards(4);
+ file.getOffsetTracker().getNewOffsettable(file, this);
+ size = file.readUInt();
+ list = new AnnotationSetRefItem[size];
+ for (int i = 0; i < size; i++) {
+ (list[i] = new AnnotationSetRefItem()).read(file);
+ }
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.alignForwards(4);
+ file.getOffsetTracker().updatePositionOfNextOffsettable(file);
+ file.writeUInt(size);
+ for (AnnotationSetRefItem annotationSetRefItem : list) {
+ annotationSetRefItem.write(file);
+ }
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ // Do nothing.
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationsDirectoryItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationsDirectoryItem.java
new file mode 100644
index 0000000..8285472
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/AnnotationsDirectoryItem.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class AnnotationsDirectoryItem implements RawDexObject {
+ public Offset classAnnotationsOff;
+ public int fieldsSize;
+ public int annotatedMethodsSize;
+ public int annotatedParametersSize;
+ public FieldAnnotation[] fieldAnnotations;
+ public MethodAnnotation[] methodAnnotations;
+ public ParameterAnnotation[] parameterAnnotations;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ file.alignForwards(4);
+ file.getOffsetTracker().getNewOffsettable(file, this);
+ classAnnotationsOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ fieldsSize = file.readUInt();
+ annotatedMethodsSize = file.readUInt();
+ annotatedParametersSize = file.readUInt();
+ if (fieldsSize != 0) {
+ fieldAnnotations = new FieldAnnotation[fieldsSize];
+ for (int i = 0; i < fieldsSize; i++) {
+ (fieldAnnotations[i] = new FieldAnnotation()).read(file);
+ }
+ }
+ if (annotatedMethodsSize != 0) {
+ methodAnnotations = new MethodAnnotation[annotatedMethodsSize];
+ for (int i = 0; i < annotatedMethodsSize; i++) {
+ (methodAnnotations[i] = new MethodAnnotation()).read(file);
+ }
+ }
+ if (annotatedParametersSize != 0) {
+ parameterAnnotations = new ParameterAnnotation[annotatedParametersSize];
+ for (int i = 0; i < annotatedParametersSize; i++) {
+ (parameterAnnotations[i] = new ParameterAnnotation()).read(file);
+ }
+ }
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.alignForwards(4);
+ file.getOffsetTracker().updatePositionOfNextOffsettable(file);
+ file.getOffsetTracker().tryToWriteOffset(classAnnotationsOff, file, false /* ULEB128 */);
+ file.writeUInt(fieldsSize);
+ file.writeUInt(annotatedMethodsSize);
+ file.writeUInt(annotatedParametersSize);
+ if (fieldAnnotations != null) {
+ for (FieldAnnotation fieldAnnotation : fieldAnnotations) {
+ fieldAnnotation.write(file);
+ }
+ }
+ if (methodAnnotations != null) {
+ for (MethodAnnotation methodAnnotation : methodAnnotations) {
+ methodAnnotation.write(file);
+ }
+ }
+ if (parameterAnnotations != null) {
+ for (ParameterAnnotation parameterAnnotation : parameterAnnotations) {
+ parameterAnnotation.write(file);
+ }
+ }
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ if (fieldAnnotations != null) {
+ for (FieldAnnotation fieldAnnotation : fieldAnnotations) {
+ fieldAnnotation.incrementIndex(kind, insertedIdx);
+ }
+ }
+ if (methodAnnotations != null) {
+ for (MethodAnnotation methodAnnotation : methodAnnotations) {
+ methodAnnotation.incrementIndex(kind, insertedIdx);
+ }
+ }
+ if (parameterAnnotations != null) {
+ for (ParameterAnnotation parameterAnnotation : parameterAnnotations) {
+ parameterAnnotation.incrementIndex(kind, insertedIdx);
+ }
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/ClassDataItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/ClassDataItem.java
new file mode 100644
index 0000000..79564bc
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/ClassDataItem.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class ClassDataItem implements RawDexObject {
+ public int staticFieldsSize;
+ public int instanceFieldsSize;
+ public int directMethodsSize;
+ public int virtualMethodsSize;
+
+ public EncodedField[] staticFields;
+ public EncodedField[] instanceFields;
+ public EncodedMethod[] directMethods;
+ public EncodedMethod[] virtualMethods;
+
+ public static class MetaInfo {
+ public ClassDefItem classDefItem;
+ }
+
+ public MetaInfo meta = new MetaInfo();
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().getNewOffsettable(file, this);
+ staticFieldsSize = file.readUleb128();
+ instanceFieldsSize = file.readUleb128();
+ directMethodsSize = file.readUleb128();
+ virtualMethodsSize = file.readUleb128();
+
+ staticFields = new EncodedField[staticFieldsSize];
+ for (int i = 0; i < staticFieldsSize; i++) {
+ (staticFields[i] = new EncodedField()).read(file);
+ }
+ instanceFields = new EncodedField[instanceFieldsSize];
+ for (int i = 0; i < instanceFieldsSize; i++) {
+ (instanceFields[i] = new EncodedField()).read(file);
+ }
+ directMethods = new EncodedMethod[directMethodsSize];
+ for (int i = 0; i < directMethodsSize; i++) {
+ (directMethods[i] = new EncodedMethod()).read(file);
+ }
+ virtualMethods = new EncodedMethod[virtualMethodsSize];
+ for (int i = 0; i < virtualMethodsSize; i++) {
+ (virtualMethods[i] = new EncodedMethod()).read(file);
+ }
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().updatePositionOfNextOffsettable(file);
+ file.writeUleb128(staticFieldsSize);
+ file.writeUleb128(instanceFieldsSize);
+ file.writeUleb128(directMethodsSize);
+ file.writeUleb128(virtualMethodsSize);
+ for (int i = 0; i < staticFieldsSize; i++) {
+ staticFields[i].write(file);
+ }
+ for (int i = 0; i < instanceFieldsSize; i++) {
+ instanceFields[i].write(file);
+ }
+ for (int i = 0; i < directMethodsSize; i++) {
+ directMethods[i].write(file);
+ }
+ for (int i = 0; i < virtualMethodsSize; i++) {
+ virtualMethods[i].write(file);
+ }
+ }
+
+ private void incrementEncodedFields(int insertedIdx, EncodedField[] fields) {
+ int fieldIdx = 0;
+ for (EncodedField field : fields) {
+ fieldIdx = field.fieldIdxDiff;
+ if (fieldIdx >= insertedIdx) {
+ field.fieldIdxDiff++;
+ // Only need to increment one, as all the others are diffed from the previous.
+ break;
+ }
+ }
+ }
+
+ private void incrementEncodedMethods(int insertedIdx, EncodedMethod[] methods) {
+ int methodIdx = 0;
+ for (EncodedMethod method : methods) {
+ methodIdx = method.methodIdxDiff;
+ if (methodIdx >= insertedIdx) {
+ method.methodIdxDiff++;
+ // Only need to increment one, as all the others are diffed from the previous.
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ if (kind == IndexUpdateKind.FIELD_ID) {
+ incrementEncodedFields(insertedIdx, staticFields);
+ incrementEncodedFields(insertedIdx, instanceFields);
+ }
+ if (kind == IndexUpdateKind.METHOD_ID) {
+ incrementEncodedMethods(insertedIdx, directMethods);
+ incrementEncodedMethods(insertedIdx, virtualMethods);
+ }
+ }
+
+ /**
+ * For a given field index, search this ClassDataItem for a definition of this field.
+ * @return null if the field isn't in this ClassDataItem.
+ */
+ public EncodedField getEncodedFieldWithIndex(int fieldIdx) {
+ int searchFieldIdx = 0;
+ for (EncodedField field : instanceFields) {
+ searchFieldIdx += field.fieldIdxDiff;
+ if (searchFieldIdx == fieldIdx) {
+ return field;
+ }
+ }
+ searchFieldIdx = 0;
+ for (EncodedField field : staticFields) {
+ searchFieldIdx += field.fieldIdxDiff;
+ if (searchFieldIdx == fieldIdx) {
+ return field;
+ }
+ }
+ return null;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/ClassDefItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/ClassDefItem.java
new file mode 100644
index 0000000..5e68d24
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/ClassDefItem.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class ClassDefItem implements RawDexObject {
+ public static int data_size = 0x20;
+
+ public int classIdx;
+ public int accessFlags;
+ public int superclassIdx;
+ public Offset interfacesOff;
+ public int sourceFileIdx;
+ public Offset annotationsOff;
+ public Offset classDataOff;
+ public Offset staticValuesOff;
+
+ public static class MetaInfo {
+ public ClassDataItem classDataItem;
+ }
+
+ public MetaInfo meta = new MetaInfo();
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().getNewOffsettable(file, this);
+ classIdx = file.readUInt();
+ accessFlags = file.readUInt();
+ superclassIdx = file.readUInt();
+ interfacesOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ sourceFileIdx = file.readUInt();
+ annotationsOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ classDataOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ staticValuesOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().updatePositionOfNextOffsettable(file);
+ file.writeUInt(classIdx);
+ file.writeUInt(accessFlags);
+ file.writeUInt(superclassIdx);
+ file.getOffsetTracker().tryToWriteOffset(interfacesOff, file, false /* ULEB128 */);
+ file.writeUInt(sourceFileIdx);
+ file.getOffsetTracker().tryToWriteOffset(annotationsOff, file, false /* ULEB128 */);
+ file.getOffsetTracker().tryToWriteOffset(classDataOff, file, false /* ULEB128 */);
+ file.getOffsetTracker().tryToWriteOffset(staticValuesOff, file, false /* ULEB128 */);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ if (kind == IndexUpdateKind.TYPE_ID && classIdx >= insertedIdx) {
+ classIdx++;
+ }
+ if (kind == IndexUpdateKind.TYPE_ID && superclassIdx >= insertedIdx) {
+ superclassIdx++;
+ }
+ if (kind == IndexUpdateKind.STRING_ID && sourceFileIdx >= insertedIdx) {
+ sourceFileIdx++;
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/CodeItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/CodeItem.java
new file mode 100644
index 0000000..af3c377
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/CodeItem.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import dexfuzz.Log;
+import dexfuzz.program.MutatableCode;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+public class CodeItem implements RawDexObject {
+ public short registersSize;
+ public short insSize;
+ public short outsSize;
+ public short triesSize;
+ public int debugInfoOff; // NB: this is a special case
+ public int insnsSize;
+ public List<Instruction> insns;
+ public TryItem[] tries;
+ public EncodedCatchHandlerList handlers;
+
+ private MutatableCode mutatableCode;
+
+ public static class MethodMetaInfo {
+ public String methodName;
+ public boolean isStatic;
+ public String shorty;
+ }
+
+ public MethodMetaInfo meta = new MethodMetaInfo();
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ file.alignForwards(4);
+ file.getOffsetTracker().getNewOffsettable(file, this);
+ registersSize = file.readUShort();
+ insSize = file.readUShort();
+ outsSize = file.readUShort();
+ triesSize = file.readUShort();
+ debugInfoOff = file.readUInt();
+ insnsSize = file.readUInt();
+ populateInstructionList(file);
+ if (triesSize > 0) {
+ if ((insnsSize % 2) != 0) {
+ // Consume padding.
+ file.readUShort();
+ }
+ tries = new TryItem[triesSize];
+ for (int i = 0; i < triesSize; i++) {
+ (tries[i] = new TryItem()).read(file);
+ }
+ (handlers = new EncodedCatchHandlerList()).read(file);
+ }
+ }
+
+ private void populateInstructionList(DexRandomAccessFile file) throws IOException {
+ insns = new LinkedList<Instruction>();
+ long insnsOffset = file.getFilePointer();
+ if (insnsOffset != 0) {
+ long finger = insnsOffset;
+ long insnsEnd = insnsOffset + (2 * insnsSize);
+
+ while (finger < insnsEnd) {
+ file.seek(finger);
+ Instruction newInsn = new Instruction();
+ newInsn.read(file);
+ insns.add(newInsn);
+ finger += (2 * newInsn.getSize());
+ }
+
+ file.seek(finger);
+ }
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.alignForwards(4);
+ file.getOffsetTracker().updatePositionOfNextOffsettable(file);
+ file.writeUShort(registersSize);
+ file.writeUShort(insSize);
+ file.writeUShort(outsSize);
+ file.writeUShort(triesSize);
+ // We do not support retaining debug info currently.
+ file.writeUInt(0 /*debug_info_off*/);
+ file.writeUInt(insnsSize);
+ for (Instruction insn : insns) {
+ insn.write(file);
+ }
+ if (triesSize > 0) {
+ if ((insnsSize % 2) != 0) {
+ // produce padding
+ file.writeUShort((short) 0);
+ }
+ for (TryItem tryItem : tries) {
+ tryItem.write(file);
+ }
+ handlers.write(file);
+ }
+ }
+
+ /**
+ * CodeTranslator should call this to notify a CodeItem about its
+ * mutatable code, so it can always get the "latest" view of its
+ * instructions.
+ */
+ public void registerMutatableCode(MutatableCode mutatableCode) {
+ this.mutatableCode = mutatableCode;
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ if (kind == IndexUpdateKind.TYPE_ID && triesSize > 0) {
+ // EncodedCatchHandlerList (well, the EncodedTypeAddrPairs it owns)
+ // are only interested in TYPE_IDs.
+ handlers.incrementIndex(kind, insertedIdx);
+ }
+
+ if (kind == IndexUpdateKind.PROTO_ID) {
+ // The only kind we can't encounter in an instruction.
+ return;
+ }
+
+ List<Instruction> insnsToIncrement = insns;
+
+ // If we have an associated MutatableCode, then it may have created some new insns
+ // that we won't know about yet, during the mutation phase.
+ //
+ // Ask for the latest view of the insns.
+ if (mutatableCode != null) {
+ insnsToIncrement = mutatableCode.requestLatestInstructions();
+ }
+
+ for (Instruction insn : insnsToIncrement) {
+ Opcode opcode = insn.info.opcode;
+ switch (kind) {
+ case STRING_ID:
+ if (opcode == Opcode.CONST_STRING || opcode == Opcode.CONST_STRING_JUMBO) {
+ // STRING@BBBB
+ if (insn.vregB >= insertedIdx) {
+ insn.vregB++;
+ }
+ }
+ break;
+ case TYPE_ID:
+ if (opcode == Opcode.CONST_CLASS
+ || opcode == Opcode.CHECK_CAST
+ || opcode == Opcode.NEW_INSTANCE
+ || opcode == Opcode.FILLED_NEW_ARRAY
+ || opcode == Opcode.FILLED_NEW_ARRAY_RANGE) {
+ // TYPE@BBBB
+ if (insn.vregB >= insertedIdx) {
+ insn.vregB++;
+ }
+ } else if (opcode == Opcode.INSTANCE_OF || opcode == Opcode.NEW_ARRAY) {
+ // TYPE@CCCC
+ if (insn.vregC >= insertedIdx) {
+ insn.vregC++;
+ }
+ }
+ break;
+ case FIELD_ID:
+ if (Opcode.isBetween(opcode, Opcode.SGET, Opcode.SPUT_SHORT)) {
+ // FIELD@BBBB
+ if (insn.vregB >= insertedIdx) {
+ insn.vregB++;
+ }
+ } else if (Opcode.isBetween(opcode, Opcode.IGET, Opcode.IPUT_SHORT)) {
+ // FIELD@CCCC
+ if (insn.vregC >= insertedIdx) {
+ insn.vregC++;
+ }
+ }
+ break;
+ case METHOD_ID:
+ if (Opcode.isBetween(opcode, Opcode.INVOKE_VIRTUAL, Opcode.INVOKE_INTERFACE)
+ || Opcode.isBetween(opcode,
+ Opcode.INVOKE_VIRTUAL_RANGE, Opcode.INVOKE_INTERFACE_RANGE)) {
+ // METHOD@BBBB
+ if (insn.vregB >= insertedIdx) {
+ insn.vregB++;
+ }
+ }
+ break;
+ default:
+ Log.errorAndQuit("Unexpected IndexUpdateKind requested "
+ + "in Instruction.incrementIndex()");
+ }
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/DebugInfoItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/DebugInfoItem.java
new file mode 100644
index 0000000..922ee58
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/DebugInfoItem.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+// Right now we are not parsing debug_info_item, just take the raw size
+public class DebugInfoItem implements RawDexObject {
+ private int size;
+ private byte[] data;
+
+ public DebugInfoItem(int size) {
+ this.size = size;
+ }
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().getNewOffsettable(file, this);
+ data = new byte[size];
+ file.read(data);
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().updatePositionOfNextOffsettable(file);
+ file.write(data);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ // Do nothing.
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/DexRandomAccessFile.java b/tools/dexfuzz/src/dexfuzz/rawdex/DexRandomAccessFile.java
new file mode 100644
index 0000000..ec75585
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/DexRandomAccessFile.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import dexfuzz.Log;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * An extension to RandomAccessFile that allows reading/writing
+ * DEX files in little-endian form, the variable-length LEB format
+ * and also provides word-alignment functions.
+ */
+public class DexRandomAccessFile extends RandomAccessFile {
+ private OffsetTracker offsetTracker;
+
+ public OffsetTracker getOffsetTracker() {
+ return offsetTracker;
+ }
+
+ public void setOffsetTracker(OffsetTracker offsetTracker) {
+ this.offsetTracker = offsetTracker;
+ }
+
+ /**
+ * Constructor, passes straight on to RandomAccessFile currently.
+ * @param filename The file to open.
+ * @param mode Strings "r" or "rw" work best.
+ */
+ public DexRandomAccessFile(String filename, String mode)
+ throws FileNotFoundException {
+ super(filename, mode);
+ }
+
+ /**
+ * @return A 16-bit number, read from the file as little-endian.
+ */
+ public short readUShort() throws IOException {
+ int b1 = readUnsignedByte();
+ int b2 = readUnsignedByte();
+ return (short) ((b2 << 8) | b1);
+ }
+
+ /**
+ * @param value A 16-bit number to be written to the file in little-endian.
+ */
+ public void writeUShort(short value) throws IOException {
+ int b1 = value & 0xff;
+ int b2 = (value & 0xff00) >> 8;
+ writeByte(b1);
+ writeByte(b2);
+ }
+
+ /**
+ * @return A 32-bit number, read from the file as little-endian.
+ */
+ public int readUInt() throws IOException {
+ int b1 = readUnsignedByte();
+ int b2 = readUnsignedByte();
+ int b3 = readUnsignedByte();
+ int b4 = readUnsignedByte();
+ return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1;
+ }
+
+ /**
+ * @param value A 32-bit number to be written to the file in little-endian.
+ */
+ public void writeUInt(int value) throws IOException {
+ int b1 = value & 0xff;
+ writeByte(b1);
+ int b2 = (value & 0xff00) >> 8;
+ writeByte(b2);
+ int b3 = (value & 0xff0000) >> 16;
+ writeByte(b3);
+ int b4 = (value & 0xff000000) >> 24;
+ writeByte(b4);
+ }
+
+ /**
+ * @return An up to 32-bit number, read from the file in ULEB128 form.
+ */
+ public int readUleb128() throws IOException {
+ int shift = 0;
+ int value = 0;
+ int rawByte = readUnsignedByte();
+ boolean done = false;
+ while (!done) {
+ // Get the lower seven bits.
+ // 0x7f = 0111 1111
+ value |= ((rawByte & 0x7f) << shift);
+ shift += 7;
+ // Check the 8th bit - if it's 0, we're done.
+ // 0x80 = 1000 0000
+ if ((rawByte & 0x80) == 0) {
+ done = true;
+ } else {
+ rawByte = readUnsignedByte();
+ }
+ }
+ return value;
+ }
+
+ /**
+ * @param value A 32-bit number to be written to the file in ULEB128 form.
+ */
+ public void writeUleb128(int value) throws IOException {
+ if (value == 0) {
+ writeByte(0);
+ return;
+ }
+
+ while (value != 0) {
+ int marker = 1;
+ // If we're down to the last 7 bits, the marker will be 0.
+ if ((value & 0xffffff80) == 0) {
+ marker = 0;
+ }
+ // Get the lowest 7 bits, add on the marker in the high bit.
+ int nextByte = value & 0x7f | (marker << 7);
+ writeByte(nextByte);
+ value >>>= 7;
+ }
+ }
+
+ /**
+ * Write out ULEB128 value always using 5 bytes.
+ * A version of ULEB128 that will always write out 5 bytes, because this
+ * value will be patched later, and if we used a smaller encoding, the new value
+ * may overflow the previously selected encoding size.
+ * The largest encoding for 0 in ULEB128 would be:
+ * 0x80 0x80 0x80 0x80 0x00
+ * and for 1 would be:
+ * 0x81 0x80 0x80 0x80 0x00
+ */
+ public void writeLargestUleb128(int value) throws IOException {
+ Log.debug("Writing " + value + " using the largest possible ULEB128 encoding.");
+ if (value == 0) {
+ writeByte(0x80);
+ writeByte(0x80);
+ writeByte(0x80);
+ writeByte(0x80);
+ writeByte(0x0);
+ return;
+ }
+
+ for (int i = 0; i < 5; i++) {
+ int marker = 1;
+ // If we're writing the 5th byte, the marker is 0.
+ if (i == 4) {
+ marker = 0;
+ }
+ // Get the lowest 7 bits, add on the marker in the high bit.
+ int nextByte = value & 0x7f | (marker << 7);
+ writeByte(nextByte);
+ value >>>= 7;
+ }
+ }
+
+ /**
+ * @return An up to 32-bit number, read from the file in SLEB128 form.
+ */
+ public int readSleb128() throws IOException {
+ int shift = 0;
+ int value = 0;
+ int rawByte = readUnsignedByte();
+ boolean done = false;
+ boolean mustSignExtend = false;
+ while (!done) {
+ // Get the lower seven bits.
+ // 0x7f = 0111 1111
+ value |= ((rawByte & 0x7f) << shift);
+ shift += 7;
+ // Check the 8th bit - if it's 0, we're done.
+ // 0x80 = 1000 0000
+ if ((rawByte & 0x80) == 0) {
+ // Check the 7th bit - if it's a 1, we need to sign extend.
+ if ((rawByte & 0x60) != 0) {
+ mustSignExtend = true;
+ }
+ done = true;
+ } else {
+ rawByte = readUnsignedByte();
+ }
+ }
+ if (mustSignExtend) {
+ // Example:
+ // say we shifted 7 bits, we need
+ // to make all the upper 25 bits 1s.
+ // load a 1...
+ // 00000000 00000000 00000000 00000001
+ // << 7
+ // 00000000 00000000 00000000 10000000
+ // - 1
+ // 00000000 00000000 00000000 01111111
+ // ~
+ // 11111111 11111111 11111111 10000000
+ int upperOnes = ~((1 << shift) - 1);
+ value |= (upperOnes);
+ }
+ return value;
+ }
+
+ /**
+ * @param value A 32-bit number to be written to the file in SLEB128 form.
+ */
+ public void writeSleb128(int value) throws IOException {
+ if (value == 0) {
+ writeByte(0);
+ return;
+ }
+ if (value > 0) {
+ writeUleb128(value);
+ return;
+ }
+ if (value == -1) {
+ writeByte(0x7f);
+ }
+
+ // When it's all 1s (0xffffffff), we're done!
+ while (value != 0xffffffff) {
+ int marker = 1;
+ // If we're down to the last 7 bits (i.e., shifting a further 7 is all 1s),
+ // the marker will be 0.
+ if ((value >> 7) == 0xffffffff) {
+ marker = 0;
+ }
+ // Get the lowest 7 bits, add on the marker in the high bit.
+ int nextByte = value & 0x7f | (marker << 7);
+ writeByte(nextByte);
+ value >>= 7;
+ }
+ }
+
+ /**
+ * In DEX format, strings are in MUTF-8 format, the first ULEB128 value is the decoded size
+ * (i.e., string.length), and then follows a null-terminated series of characters.
+ * @param decodedSize The ULEB128 value that should have been read just before this.
+ * @return The raw bytes of the string, not including the null character.
+ */
+ public byte[] readDexUtf(int decodedSize) throws IOException {
+ // In the dex MUTF-8, the encoded size can never be larger than 3 times
+ // the actual string's length (which is the ULEB128 value just before this
+ // string, the "decoded size")
+
+ // Therefore, allocate as much space as we might need.
+ byte[] str = new byte[decodedSize * 3];
+
+ // Get our first byte.
+ int encodedSize = 0;
+ byte rawByte = readByte();
+
+ // Keep reading until we find the end marker.
+ while (rawByte != 0) {
+ str[encodedSize++] = rawByte;
+ rawByte = readByte();
+ }
+
+ // Copy everything we read into str into the correctly-sized actual string.
+ byte[] actualString = new byte[encodedSize];
+ for (int i = 0; i < encodedSize; i++) {
+ actualString[i] = str[i];
+ }
+
+ return actualString;
+ }
+
+ /**
+ * Writes out raw bytes that would have been read by readDexUTF().
+ * Will automatically write out the null-byte at the end.
+ * @param data Bytes to be written out.
+ */
+ public void writeDexUtf(byte[] data) throws IOException {
+ write(data);
+ // Remember to add the end marker.
+ writeByte(0);
+ }
+
+ /**
+ * Align the file handle's seek pointer to the next N bytes.
+ * @param alignment N to align to.
+ */
+ public void alignForwards(int alignment) throws IOException {
+ long offset = getFilePointer();
+ long mask = alignment - 1;
+ if ((offset & mask) != 0) {
+ int extra = alignment - (int) (offset & mask);
+ seek(offset + extra);
+ }
+ }
+
+ /**
+ * Align the file handle's seek pointer backwards to the previous N bytes.
+ * @param alignment N to align to.
+ */
+ public void alignBackwards(int alignment) throws IOException {
+ long offset = getFilePointer();
+ long mask = alignment - 1;
+ if ((offset & mask) != 0) {
+ offset &= (~mask);
+ seek(offset);
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/EncodedAnnotation.java b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedAnnotation.java
new file mode 100644
index 0000000..085a4a8
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedAnnotation.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class EncodedAnnotation implements RawDexObject {
+ public int typeIdx;
+ public int size;
+ public AnnotationElement[] elements;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ typeIdx = file.readUleb128();
+ size = file.readUleb128();
+ if (size != 0) {
+ elements = new AnnotationElement[size];
+ for (int i = 0; i < size; i++) {
+ (elements[i] = new AnnotationElement()).read(file);
+ }
+ }
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.writeUleb128(typeIdx);
+ file.writeUleb128(size);
+ if (size != 0) {
+ for (AnnotationElement annotationElement : elements) {
+ annotationElement.write(file);
+ }
+ }
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ if (kind == IndexUpdateKind.TYPE_ID && typeIdx >= insertedIdx) {
+ typeIdx++;
+ }
+ if (size != 0) {
+ for (AnnotationElement element : elements) {
+ element.incrementIndex(kind, insertedIdx);
+ }
+ }
+ }
+
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/EncodedArray.java b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedArray.java
new file mode 100644
index 0000000..267ff09
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedArray.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class EncodedArray implements RawDexObject {
+ public int size;
+ public EncodedValue[] values;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ size = file.readUleb128();
+ if (size != 0) {
+ values = new EncodedValue[size];
+ for (int i = 0; i < size; i++) {
+ (values[i] = new EncodedValue()).read(file);
+ }
+ }
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.writeUleb128(size);
+ if (size != 0) {
+ for (EncodedValue encodedValue : values) {
+ encodedValue.write(file);
+ }
+ }
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ if (size != 0) {
+ for (EncodedValue value : values) {
+ value.incrementIndex(kind, insertedIdx);
+ }
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/EncodedArrayItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedArrayItem.java
new file mode 100644
index 0000000..e461a54
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedArrayItem.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class EncodedArrayItem implements RawDexObject {
+ public EncodedArray value;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().getNewOffsettable(file, this);
+ (value = new EncodedArray()).read(file);
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().updatePositionOfNextOffsettable(file);
+ value.write(file);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ // Do nothing.
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/EncodedCatchHandler.java b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedCatchHandler.java
new file mode 100644
index 0000000..83786c8
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedCatchHandler.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class EncodedCatchHandler implements RawDexObject {
+ public int size;
+ public EncodedTypeAddrPair[] handlers;
+ public int catchAllAddr;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ size = file.readSleb128();
+ int absoluteSize = Math.abs(size);
+ if (absoluteSize > 0) {
+ handlers = new EncodedTypeAddrPair[absoluteSize];
+ for (int i = 0; i < Math.abs(size); i++) {
+ (handlers[i] = new EncodedTypeAddrPair()).read(file);
+ }
+ }
+ if (size <= 0) {
+ catchAllAddr = file.readUleb128();
+ }
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.writeSleb128(size);
+ if (handlers != null) {
+ for (EncodedTypeAddrPair encodedTypeAddrPair : handlers) {
+ encodedTypeAddrPair.write(file);
+ }
+ }
+ if (size <= 0) {
+ file.writeUleb128(catchAllAddr);
+ }
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ if (handlers != null) {
+ for (EncodedTypeAddrPair handler : handlers) {
+ handler.incrementIndex(kind, insertedIdx);
+ }
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/EncodedCatchHandlerList.java b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedCatchHandlerList.java
new file mode 100644
index 0000000..5619b5f
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedCatchHandlerList.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class EncodedCatchHandlerList implements RawDexObject {
+ public int size;
+ public EncodedCatchHandler[] list;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ size = file.readUleb128();
+ list = new EncodedCatchHandler[size];
+ for (int i = 0; i < size; i++) {
+ (list[i] = new EncodedCatchHandler()).read(file);
+ }
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.writeUleb128(size);
+ for (EncodedCatchHandler encodedCatchHandler : list) {
+ encodedCatchHandler.write(file);
+ }
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ for (EncodedCatchHandler handler : list) {
+ handler.incrementIndex(kind, insertedIdx);
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/EncodedField.java b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedField.java
new file mode 100644
index 0000000..18c1d43
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedField.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class EncodedField implements RawDexObject {
+ public int fieldIdxDiff;
+ public int accessFlags;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ fieldIdxDiff = file.readUleb128();
+ accessFlags = file.readUleb128();
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.writeUleb128(fieldIdxDiff);
+ file.writeUleb128(accessFlags);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ // Do nothing.
+ // NB: our idx_diff is handled in ClassDataItem...
+ }
+
+ public boolean isVolatile() {
+ return ((accessFlags & Flag.ACC_VOLATILE.getValue()) != 0);
+ }
+
+ /**
+ * Set/unset the volatile flag for this EncodedField.
+ */
+ public void setVolatile(boolean turnOn) {
+ if (turnOn) {
+ accessFlags |= Flag.ACC_VOLATILE.getValue();
+ } else {
+ accessFlags &= ~(Flag.ACC_VOLATILE.getValue());
+ }
+ }
+
+ private static enum Flag {
+ ACC_PUBLIC(0x1),
+ ACC_PRIVATE(0x2),
+ ACC_PROTECTED(0x4),
+ ACC_STATIC(0x8),
+ ACC_FINAL(0x10),
+ ACC_VOLATILE(0x40),
+ ACC_TRANSIENT(0x80),
+ ACC_SYNTHETIC(0x1000),
+ ACC_ENUM(0x4000);
+
+ private int value;
+
+ private Flag(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/EncodedMethod.java b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedMethod.java
new file mode 100644
index 0000000..3a8163a
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedMethod.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import dexfuzz.Log;
+
+import java.io.IOException;
+
+public class EncodedMethod implements RawDexObject {
+ public int methodIdxDiff;
+ public int accessFlags;
+ public Offset codeOff;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ methodIdxDiff = file.readUleb128();
+ accessFlags = file.readUleb128();
+ codeOff = file.getOffsetTracker().getNewOffset(file.readUleb128());
+ if (isNative()) {
+ Log.errorAndQuit("Sorry, DEX files with native methods are not supported yet.");
+ }
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.writeUleb128(methodIdxDiff);
+ file.writeUleb128(accessFlags);
+ file.getOffsetTracker().tryToWriteOffset(codeOff, file, true /* ULEB128 */);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ // Do nothing.
+ // NB: our idx_diff is handled in ClassDataItem...
+ }
+
+ public boolean isStatic() {
+ return ((accessFlags & Flag.ACC_STATIC.getValue()) != 0);
+ }
+
+ public boolean isNative() {
+ return ((accessFlags & Flag.ACC_NATIVE.getValue()) != 0);
+ }
+
+ /**
+ * Set/unset the static flag for this EncodedMethod.
+ */
+ public void setStatic(boolean turnOn) {
+ if (turnOn) {
+ accessFlags |= Flag.ACC_STATIC.getValue();
+ } else {
+ accessFlags &= ~(Flag.ACC_STATIC.getValue());
+ }
+ }
+
+ private static enum Flag {
+ ACC_PUBLIC(0x1),
+ ACC_PRIVATE(0x2),
+ ACC_PROTECTED(0x4),
+ ACC_STATIC(0x8),
+ ACC_FINAL(0x10),
+ ACC_SYNCHRONIZED(0x20),
+ ACC_VARARGS(0x80),
+ ACC_NATIVE(0x100),
+ ACC_ABSTRACT(0x400),
+ ACC_STRICT(0x800),
+ ACC_SYNTHETIC(0x1000),
+ ACC_ENUM(0x4000),
+ ACC_CONSTRUCTOR(0x10000);
+
+ private int value;
+
+ private Flag(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/EncodedTypeAddrPair.java b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedTypeAddrPair.java
new file mode 100644
index 0000000..ea49ae1
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedTypeAddrPair.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class EncodedTypeAddrPair implements RawDexObject {
+ public int typeIdx;
+ public int addr;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ typeIdx = file.readUleb128();
+ addr = file.readUleb128();
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.writeUleb128(typeIdx);
+ file.writeUleb128(addr);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ if (kind == IndexUpdateKind.TYPE_ID && typeIdx >= insertedIdx) {
+ typeIdx++;
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/EncodedValue.java b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedValue.java
new file mode 100644
index 0000000..fdf133f
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/EncodedValue.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class EncodedValue implements RawDexObject {
+ public byte valueArg;
+ public byte valueType;
+ public byte[] value;
+ public EncodedArray encodedArray;
+ public EncodedAnnotation encodedAnnotation;
+
+ private static final byte VALUE_BYTE = 0x00;
+ private static final byte VALUE_ARRAY = 0x1c;
+ private static final byte VALUE_ANNOTATION = 0x1d;
+ private static final byte VALUE_NULL = 0x1e;
+ private static final byte VALUE_BOOLEAN = 0x1f;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ int valueArgAndType = file.readUnsignedByte();
+
+ // Get lower 5 bits.
+ valueType = (byte) (valueArgAndType & 0x1f);
+ // Get upper 3 bits.
+ valueArg = (byte) ((valueArgAndType & 0xe0) >> 5);
+
+ int size = 0;
+
+ switch (valueType) {
+ case VALUE_BYTE:
+ size = 1;
+ break;
+ case VALUE_ARRAY:
+ (encodedArray = new EncodedArray()).read(file);
+ size = 0; // So we don't read into value.
+ break;
+ case VALUE_ANNOTATION:
+ (encodedAnnotation = new EncodedAnnotation()).read(file);
+ size = 0; // So we don't read into value.
+ break;
+ case VALUE_NULL:
+ case VALUE_BOOLEAN:
+ // No value
+ size = 0;
+ break;
+ default:
+ // All others encode value_arg as (size - 1), so...
+ size = valueArg + 1;
+ break;
+ }
+
+ if (size != 0) {
+ value = new byte[size];
+ for (int i = 0; i < size; i++) {
+ value[i] = file.readByte();
+ }
+ }
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ int valueArgAndType = ((valueType) | (valueArg << 5));
+ file.writeByte(valueArgAndType);
+
+ if (encodedArray != null) {
+ encodedArray.write(file);
+ } else if (encodedAnnotation != null) {
+ encodedAnnotation.write(file);
+ } else if (value != null) {
+ file.write(value);
+ }
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ if (encodedArray != null) {
+ encodedArray.incrementIndex(kind, insertedIdx);
+ } else if (encodedAnnotation != null) {
+ encodedAnnotation.incrementIndex(kind, insertedIdx);
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/FieldAnnotation.java b/tools/dexfuzz/src/dexfuzz/rawdex/FieldAnnotation.java
new file mode 100644
index 0000000..98f812e
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/FieldAnnotation.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class FieldAnnotation implements RawDexObject {
+ public int fieldIdx;
+ public Offset annotationsOff;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ fieldIdx = file.readUInt();
+ annotationsOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.writeUInt(fieldIdx);
+ file.getOffsetTracker().tryToWriteOffset(annotationsOff, file, false /* ULEB128 */);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ if (kind == IndexUpdateKind.FIELD_ID && fieldIdx >= insertedIdx) {
+ fieldIdx++;
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/FieldIdItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/FieldIdItem.java
new file mode 100644
index 0000000..75f2078
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/FieldIdItem.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class FieldIdItem implements RawDexObject {
+ public short classIdx;
+ public short typeIdx;
+ public int nameIdx;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().getNewOffsettable(file, this);
+ classIdx = file.readUShort();
+ typeIdx = file.readUShort();
+ nameIdx = file.readUInt();
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().updatePositionOfNextOffsettable(file);
+ file.writeUShort(classIdx);
+ file.writeUShort(typeIdx);
+ file.writeUInt(nameIdx);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ if (kind == IndexUpdateKind.TYPE_ID && classIdx >= insertedIdx) {
+ classIdx++;
+ }
+ if (kind == IndexUpdateKind.TYPE_ID && typeIdx >= insertedIdx) {
+ typeIdx++;
+ }
+ if (kind == IndexUpdateKind.STRING_ID && nameIdx >= insertedIdx) {
+ nameIdx++;
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/HeaderItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/HeaderItem.java
new file mode 100644
index 0000000..e6b290c
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/HeaderItem.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import dexfuzz.Log;
+
+import java.io.IOException;
+
+public class HeaderItem implements RawDexObject {
+ public byte[] magic;
+ public int checksum;
+ public byte[] signature; // Verification doesn't depend on this, so we don't update it.
+ public int fileSize;
+ public int headerSize;
+ public int endianTag;
+ public int linkSize;
+ public Offset linkOff;
+ public Offset mapOff;
+ public int stringIdsSize;
+ public Offset stringIdsOff;
+ public int typeIdsSize;
+ public Offset typeIdsOff;
+ public int protoIdsSize;
+ public Offset protoIdsOff;
+ public int fieldIdsSize;
+ public Offset fieldIdsOff;
+ public int methodIdsSize;
+ public Offset methodIdsOff;
+ public int classDefsSize;
+ public Offset classDefsOff;
+ public int dataSize;
+ public Offset dataOff;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().getNewOffsettable(file, this);
+ magic = new byte[8];
+ for (int i = 0; i < 8; i++) {
+ magic[i] = file.readByte();
+ }
+ checksum = file.readUInt();
+ signature = new byte[20];
+ for (int i = 0; i < 20; i++) {
+ signature[i] = file.readByte();
+ }
+ fileSize = file.readUInt();
+ headerSize = file.readUInt();
+ endianTag = file.readUInt();
+ linkSize = file.readUInt();
+ linkOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ mapOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ stringIdsSize = file.readUInt();
+ stringIdsOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ typeIdsSize = file.readUInt();
+ typeIdsOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ protoIdsSize = file.readUInt();
+ protoIdsOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ fieldIdsSize = file.readUInt();
+ fieldIdsOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ methodIdsSize = file.readUInt();
+ methodIdsOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ classDefsSize = file.readUInt();
+ classDefsOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ dataSize = file.readUInt();
+ dataOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ if (headerSize != 0x70) {
+ Log.errorAndQuit("Invalid header size in header.");
+ }
+ if (file.getFilePointer() != headerSize) {
+ Log.errorAndQuit("Read a different amount than expected in header: "
+ + file.getFilePointer());
+ }
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().updatePositionOfNextOffsettable(file);
+ for (int i = 0; i < 8; i++) {
+ file.writeByte(magic[i]);
+ }
+ // Will be recalculated later!
+ file.writeUInt(checksum);
+ for (int i = 0; i < 20; i++) {
+ file.writeByte(signature[i]);
+ }
+ // Will be recalculated later!
+ file.writeUInt(fileSize);
+ file.writeUInt(headerSize);
+ file.writeUInt(endianTag);
+ file.writeUInt(linkSize);
+ file.getOffsetTracker().tryToWriteOffset(linkOff, file, false /* ULEB128 */);
+ file.getOffsetTracker().tryToWriteOffset(mapOff, file, false /* ULEB128 */);
+ file.writeUInt(stringIdsSize);
+ file.getOffsetTracker().tryToWriteOffset(stringIdsOff, file, false /* ULEB128 */);
+ file.writeUInt(typeIdsSize);
+ file.getOffsetTracker().tryToWriteOffset(typeIdsOff, file, false /* ULEB128 */);
+ file.writeUInt(protoIdsSize);
+ file.getOffsetTracker().tryToWriteOffset(protoIdsOff, file, false /* ULEB128 */);
+ file.writeUInt(fieldIdsSize);
+ file.getOffsetTracker().tryToWriteOffset(fieldIdsOff, file, false /* ULEB128 */);
+ file.writeUInt(methodIdsSize);
+ file.getOffsetTracker().tryToWriteOffset(methodIdsOff, file, false /* ULEB128 */);
+ file.writeUInt(classDefsSize);
+ file.getOffsetTracker().tryToWriteOffset(classDefsOff, file, false /* ULEB128 */);
+ // will be recalculated later
+ file.writeUInt(dataSize);
+ file.getOffsetTracker().tryToWriteOffset(dataOff, file, false /* ULEB128 */);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ // Do nothing
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/Instruction.java b/tools/dexfuzz/src/dexfuzz/rawdex/Instruction.java
new file mode 100644
index 0000000..2dda78f
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/Instruction.java
@@ -0,0 +1,584 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import dexfuzz.Log;
+import dexfuzz.rawdex.formats.AbstractFormat;
+import dexfuzz.rawdex.formats.ContainsConst;
+import dexfuzz.rawdex.formats.ContainsPoolIndex;
+import dexfuzz.rawdex.formats.ContainsTarget;
+import dexfuzz.rawdex.formats.ContainsVRegs;
+import dexfuzz.rawdex.formats.Format10t;
+import dexfuzz.rawdex.formats.Format10x;
+import dexfuzz.rawdex.formats.Format11n;
+import dexfuzz.rawdex.formats.Format11x;
+import dexfuzz.rawdex.formats.Format12x;
+import dexfuzz.rawdex.formats.Format20t;
+import dexfuzz.rawdex.formats.Format21c;
+import dexfuzz.rawdex.formats.Format21h;
+import dexfuzz.rawdex.formats.Format21s;
+import dexfuzz.rawdex.formats.Format21t;
+import dexfuzz.rawdex.formats.Format22b;
+import dexfuzz.rawdex.formats.Format22c;
+import dexfuzz.rawdex.formats.Format22s;
+import dexfuzz.rawdex.formats.Format22t;
+import dexfuzz.rawdex.formats.Format22x;
+import dexfuzz.rawdex.formats.Format23x;
+import dexfuzz.rawdex.formats.Format30t;
+import dexfuzz.rawdex.formats.Format31c;
+import dexfuzz.rawdex.formats.Format31i;
+import dexfuzz.rawdex.formats.Format31t;
+import dexfuzz.rawdex.formats.Format32x;
+import dexfuzz.rawdex.formats.Format35c;
+import dexfuzz.rawdex.formats.Format3rc;
+import dexfuzz.rawdex.formats.Format51l;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class Instruction implements RawDexObject {
+ // Only used by Format35* instructions
+ public static class InvokeFormatInfo {
+ public byte vregD;
+ public byte vregE;
+ public byte vregF;
+ public byte vregG;
+ }
+
+ // Immutable information about this class of instruction.
+ public OpcodeInfo info;
+
+ // The raw bytes of the instruction.
+ // Only used during reading, and writing out is done from the decoded instruction data.
+ // Except in the case of the 3 "data" instructions.
+ public byte[] rawBytes;
+
+ public static final int RAW_TYPE_PACKED_SWITCH_DATA = 1;
+ public static final int RAW_TYPE_SPARSE_SWITCH_DATA = 2;
+ public static final int RAW_TYPE_FILL_ARRAY_DATA_DATA = 3;
+
+ public int rawType;
+ public boolean justRaw;
+ public int rawSize;
+
+ public long vregA = 0;
+ public long vregB = 0;
+ public long vregC = 0;
+
+ public InvokeFormatInfo invokeFormatInfo;
+
+ /**
+ * Clone an instruction.
+ */
+ public Instruction clone() {
+ Instruction newInsn = new Instruction();
+ // If we've generated a new instruction, we won't have calculated its raw array.
+ if (newInsn.rawBytes != null) {
+ newInsn.rawBytes = new byte[rawBytes.length];
+ for (int i = 0; i < rawBytes.length; i++) {
+ newInsn.rawBytes[i] = rawBytes[i];
+ }
+ }
+ newInsn.justRaw = justRaw;
+ newInsn.rawType = rawType;
+ newInsn.rawSize = rawSize;
+
+ newInsn.vregA = vregA;
+ newInsn.vregB = vregB;
+ newInsn.vregC = vregC;
+ newInsn.info = info;
+ if (invokeFormatInfo != null) {
+ newInsn.invokeFormatInfo = new InvokeFormatInfo();
+ newInsn.invokeFormatInfo.vregD = invokeFormatInfo.vregD;
+ newInsn.invokeFormatInfo.vregE = invokeFormatInfo.vregE;
+ newInsn.invokeFormatInfo.vregF = invokeFormatInfo.vregF;
+ newInsn.invokeFormatInfo.vregG = invokeFormatInfo.vregG;
+ }
+ return newInsn;
+ }
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ // Remember the offset, so after reading the opcode, we can read the whole
+ // insn into raw_bytes.
+ long offset = file.getFilePointer();
+ int opcodeValue = readOpcode(file);
+ info = getOpcodeInfo(opcodeValue);
+ if (info == null) {
+ Log.errorAndQuit("Couldn't find OpcodeInfo for opcode with value: "
+ + opcodeValue);
+ }
+
+ rawBytes = new byte[2 * getSize()];
+ file.seek(offset);
+ file.read(rawBytes);
+
+ vregA = info.format.getA(rawBytes);
+ vregB = info.format.getB(rawBytes);
+ vregC = info.format.getC(rawBytes);
+
+ // Special case for 35* formats.
+ if (info.format.needsInvokeFormatInfo()) {
+ invokeFormatInfo = new InvokeFormatInfo();
+ invokeFormatInfo.vregD = (byte) (rawBytes[4] >> 4);
+ invokeFormatInfo.vregE = (byte) (rawBytes[5] & 0xf);
+ invokeFormatInfo.vregF = (byte) (rawBytes[5] >> 4);
+ invokeFormatInfo.vregG = (byte) (rawBytes[1] & 0xf);
+ }
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ if (justRaw) {
+ // It is the responsibility of the CodeTranslator to make
+ // sure the raw bytes have been updated.
+ file.write(rawBytes);
+ } else {
+ info.format.writeToFile(file, this);
+ }
+ }
+
+ /**
+ * Get the size of an instruction, in code-words. (Code-words are 16-bits.)
+ */
+ public int getSize() {
+ if (justRaw) {
+ // It is the responsibility of the CodeTranslator to make sure
+ // the raw size has been updated.
+ return rawSize;
+ }
+ return info.format.getSize();
+ }
+
+ private int readOpcode(DexRandomAccessFile file) throws IOException {
+ short firstCodeWord = file.readUShort();
+ int opcode = (firstCodeWord & 0xff);
+ int upperBits = (firstCodeWord & 0xff00) >> 8;
+ if (opcode == 0x0 && upperBits != 0x0) {
+ justRaw = true;
+ rawType = upperBits;
+ // Need to calculate special sizes.
+ switch (rawType) {
+ case RAW_TYPE_PACKED_SWITCH_DATA:
+ rawSize = (file.readUShort() * 2) + 4;
+ break;
+ case RAW_TYPE_SPARSE_SWITCH_DATA:
+ rawSize = (file.readUShort() * 4) + 2;
+ break;
+ case RAW_TYPE_FILL_ARRAY_DATA_DATA:
+ {
+ int elementWidth = file.readUShort();
+ rawSize = ((file.readUInt() * elementWidth + 1) / 2) + 4;
+ break;
+ }
+ default:
+ Log.errorAndQuit("Unrecognised ident in data-payload instruction: " + rawType);
+ }
+ }
+ return opcode;
+ }
+
+ @Override
+ public String toString() {
+ if (justRaw) {
+ switch (rawType) {
+ case RAW_TYPE_PACKED_SWITCH_DATA:
+ return "PACKED SWITCH DATA";
+ case RAW_TYPE_SPARSE_SWITCH_DATA:
+ return "SPARSE SWITCH DATA";
+ case RAW_TYPE_FILL_ARRAY_DATA_DATA:
+ return "FILL ARRAY DATA DATA";
+ default:
+ }
+
+ }
+
+ String repr = info.name;
+
+ AbstractFormat format = info.format;
+
+ if (invokeFormatInfo != null) {
+ String vregs = "";
+
+ int numVregs = (int) vregA;
+
+ if (numVregs > 5) {
+ Log.debug("vA in an 35c invoke was greater than 5? Assuming 5.");
+ numVregs = 5;
+ } else if (numVregs < 0) {
+ Log.debug("vA in an 35c invoke was less than 0? Assuming 0.");
+ numVregs = 0;
+ }
+
+ switch (numVregs) {
+ case 5:
+ vregs = ", v" + invokeFormatInfo.vregG;
+ // fallthrough
+ case 4:
+ vregs = ", v" + invokeFormatInfo.vregF + vregs;
+ // fallthrough
+ case 3:
+ vregs = ", v" + invokeFormatInfo.vregE + vregs;
+ // fallthrough
+ case 2:
+ vregs = ", v" + invokeFormatInfo.vregD + vregs;
+ // fallthrough
+ case 1:
+ vregs = "v" + vregC + vregs;
+ break;
+ default:
+ }
+
+ repr += "(" + vregs + ")";
+
+ long poolIndex = ((ContainsPoolIndex)format).getPoolIndex(this);
+ repr += " meth@" + poolIndex;
+
+ return repr;
+ }
+
+
+
+ if (format instanceof ContainsVRegs) {
+ String vregs = "";
+ switch (((ContainsVRegs)format).getVRegCount()) {
+ case 3:
+ vregs = ", v" + vregC;
+ // fallthrough
+ case 2:
+ vregs = ", v" + vregB + vregs;
+ // fallthrough
+ case 1:
+ vregs = "v" + vregA + vregs;
+ break;
+ default:
+ Log.errorAndQuit("Invalid number of vregs reported by a Format.");
+ }
+
+ repr += " " + vregs;
+ }
+ if (format instanceof ContainsConst) {
+ long constant = ((ContainsConst)format).getConst(this);
+ repr += " #" + constant;
+ }
+ if (format instanceof ContainsPoolIndex) {
+ long poolIndex = ((ContainsPoolIndex)format).getPoolIndex(this);
+ repr += " pool@" + poolIndex;
+ }
+ if (format instanceof ContainsTarget) {
+ long target = ((ContainsTarget)format).getTarget(this);
+ repr += " +" + target;
+ }
+
+ return repr;
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ // Do nothing.
+ }
+
+ // STATIC INSTRUCTION CODE
+ private static Map<Integer,OpcodeInfo> opcode_map_by_int = new HashMap<Integer,OpcodeInfo>();
+ private static Map<Opcode,OpcodeInfo> opcode_map_by_enum = new HashMap<Opcode,OpcodeInfo>();
+
+ public static OpcodeInfo getOpcodeInfo(Opcode opcode) {
+ return opcode_map_by_enum.get(opcode);
+ }
+
+ public static OpcodeInfo getOpcodeInfo(int opcodeValue) {
+ return opcode_map_by_int.get(opcodeValue);
+ }
+
+ private static void addOpcodeInfo(Opcode opcode, String name,
+ int opcodeValue, AbstractFormat fmt) {
+ OpcodeInfo info = new OpcodeInfo(opcode, name, opcodeValue, fmt);
+ if (opcode.ordinal() != opcodeValue) {
+ Log.errorAndQuit(String.format("Opcode: %s (enum ordinal 0x%x) != (value 0x%x)",
+ opcode.toString(), opcode.ordinal(), opcodeValue));
+ }
+ opcode_map_by_int.put(opcodeValue, info);
+ opcode_map_by_enum.put(opcode, info);
+ }
+
+ static {
+ addOpcodeInfo(Opcode.NOP, "nop", 0x00, new Format10x());
+ addOpcodeInfo(Opcode.MOVE, "move", 0x01, new Format12x());
+ addOpcodeInfo(Opcode.MOVE_FROM16, "move/from16", 0x02, new Format22x());
+ addOpcodeInfo(Opcode.MOVE_16, "move/16", 0x03, new Format32x());
+ addOpcodeInfo(Opcode.MOVE_WIDE, "move-wide", 0x04, new Format12x());
+ addOpcodeInfo(Opcode.MOVE_WIDE_FROM16, "move-wide/from16", 0x05, new Format22x());
+ addOpcodeInfo(Opcode.MOVE_WIDE_16, "move-wide/16", 0x06, new Format32x());
+ addOpcodeInfo(Opcode.MOVE_OBJECT, "move-object", 0x07, new Format12x());
+ addOpcodeInfo(Opcode.MOVE_OBJECT_FROM16, "move-object/from16", 0x08, new Format22x());
+ addOpcodeInfo(Opcode.MOVE_OBJECT_16, "move-object/16", 0x09, new Format32x());
+ addOpcodeInfo(Opcode.MOVE_RESULT, "move-result", 0x0a, new Format11x());
+ addOpcodeInfo(Opcode.MOVE_RESULT_WIDE, "move-result-wide", 0x0b, new Format11x());
+ addOpcodeInfo(Opcode.MOVE_RESULT_OBJECT, "move-result-object", 0x0c, new Format11x());
+ addOpcodeInfo(Opcode.MOVE_EXCEPTION, "move-exception", 0x0d, new Format11x());
+ addOpcodeInfo(Opcode.RETURN_VOID, "return-void", 0x0e, new Format10x());
+ addOpcodeInfo(Opcode.RETURN, "return", 0x0f, new Format11x());
+ addOpcodeInfo(Opcode.RETURN_WIDE, "return-wide", 0x10, new Format11x());
+ addOpcodeInfo(Opcode.RETURN_OBJECT, "return-object", 0x11, new Format11x());
+ addOpcodeInfo(Opcode.CONST_4, "const/4", 0x12, new Format11n());
+ addOpcodeInfo(Opcode.CONST_16, "const/16", 0x13, new Format21s());
+ addOpcodeInfo(Opcode.CONST, "const", 0x14, new Format31i());
+ addOpcodeInfo(Opcode.CONST_HIGH16, "const/high16", 0x15, new Format21h());
+ addOpcodeInfo(Opcode.CONST_WIDE_16, "const-wide/16", 0x16, new Format21s());
+ addOpcodeInfo(Opcode.CONST_WIDE_32, "const-wide/32", 0x17, new Format31i());
+ addOpcodeInfo(Opcode.CONST_WIDE, "const-wide", 0x18, new Format51l());
+ addOpcodeInfo(Opcode.CONST_WIDE_HIGH16, "const-wide/high16", 0x19, new Format21h());
+ addOpcodeInfo(Opcode.CONST_STRING, "const-string", 0x1a, new Format21c());
+ addOpcodeInfo(Opcode.CONST_STRING_JUMBO, "const-string/jumbo", 0x1b, new Format31c());
+ addOpcodeInfo(Opcode.CONST_CLASS, "const-class", 0x1c, new Format21c());
+ addOpcodeInfo(Opcode.MONITOR_ENTER, "monitor-enter", 0x1d, new Format11x());
+ addOpcodeInfo(Opcode.MONITOR_EXIT, "monitor-exit", 0x1e, new Format11x());
+ addOpcodeInfo(Opcode.CHECK_CAST, "check-cast", 0x1f, new Format21c());
+ addOpcodeInfo(Opcode.INSTANCE_OF, "instance-of", 0x20, new Format22c());
+ addOpcodeInfo(Opcode.ARRAY_LENGTH, "array-length", 0x21, new Format12x());
+ addOpcodeInfo(Opcode.NEW_INSTANCE, "new-instance", 0x22, new Format21c());
+ addOpcodeInfo(Opcode.NEW_ARRAY, "new-array", 0x23, new Format22c());
+ addOpcodeInfo(Opcode.FILLED_NEW_ARRAY, "filled-new-array", 0x24, new Format35c());
+ addOpcodeInfo(Opcode.FILLED_NEW_ARRAY_RANGE, "filled-new-array/range",
+ 0x25, new Format3rc());
+ addOpcodeInfo(Opcode.FILL_ARRAY_DATA, "fill-array-data", 0x26, new Format31t());
+ addOpcodeInfo(Opcode.THROW, "throw", 0x27, new Format11x());
+ addOpcodeInfo(Opcode.GOTO, "goto", 0x28, new Format10t());
+ addOpcodeInfo(Opcode.GOTO_16, "goto/16", 0x29, new Format20t());
+ addOpcodeInfo(Opcode.GOTO_32, "goto/32", 0x2a, new Format30t());
+ addOpcodeInfo(Opcode.PACKED_SWITCH, "packed-switch", 0x2b, new Format31t());
+ addOpcodeInfo(Opcode.SPARSE_SWITCH, "sparse-switch", 0x2c, new Format31t());
+ addOpcodeInfo(Opcode.CMPL_FLOAT, "cmpl-float", 0x2d, new Format23x());
+ addOpcodeInfo(Opcode.CMPG_FLOAT, "cmpg-float", 0x2e, new Format23x());
+ addOpcodeInfo(Opcode.CMPL_DOUBLE, "cmpl-double", 0x2f, new Format23x());
+ addOpcodeInfo(Opcode.CMPG_DOUBLE, "cmpg-double", 0x30, new Format23x());
+ addOpcodeInfo(Opcode.CMP_LONG, "cmp-long", 0x31, new Format23x());
+ addOpcodeInfo(Opcode.IF_EQ, "if-eq", 0x32, new Format22t());
+ addOpcodeInfo(Opcode.IF_NE, "if-ne", 0x33, new Format22t());
+ addOpcodeInfo(Opcode.IF_LT, "if-lt", 0x34, new Format22t());
+ addOpcodeInfo(Opcode.IF_GE, "if-ge", 0x35, new Format22t());
+ addOpcodeInfo(Opcode.IF_GT, "if-gt", 0x36, new Format22t());
+ addOpcodeInfo(Opcode.IF_LE, "if-le", 0x37, new Format22t());
+ addOpcodeInfo(Opcode.IF_EQZ, "if-eqz", 0x38, new Format21t());
+ addOpcodeInfo(Opcode.IF_NEZ, "if-nez", 0x39, new Format21t());
+ addOpcodeInfo(Opcode.IF_LTZ, "if-ltz", 0x3a, new Format21t());
+ addOpcodeInfo(Opcode.IF_GEZ, "if-gez", 0x3b, new Format21t());
+ addOpcodeInfo(Opcode.IF_GTZ, "if-gtz", 0x3c, new Format21t());
+ addOpcodeInfo(Opcode.IF_LEZ, "if-lez", 0x3d, new Format21t());
+ addOpcodeInfo(Opcode.UNUSED_3E, "unused-3e", 0x3e, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_3F, "unused-3f", 0x3f, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_40, "unused-40", 0x40, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_41, "unused-41", 0x41, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_42, "unused-42", 0x42, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_43, "unused-43", 0x43, new Format10x());
+ addOpcodeInfo(Opcode.AGET, "aget", 0x44, new Format23x());
+ addOpcodeInfo(Opcode.AGET_WIDE, "aget-wide", 0x45, new Format23x());
+ addOpcodeInfo(Opcode.AGET_WIDE, "aget-wide", 0x45, new Format23x());
+ addOpcodeInfo(Opcode.AGET_OBJECT, "aget-object", 0x46, new Format23x());
+ addOpcodeInfo(Opcode.AGET_BOOLEAN, "aget-boolean", 0x47, new Format23x());
+ addOpcodeInfo(Opcode.AGET_BYTE, "aget-byte", 0x48, new Format23x());
+ addOpcodeInfo(Opcode.AGET_CHAR, "aget-char", 0x49, new Format23x());
+ addOpcodeInfo(Opcode.AGET_SHORT, "aget-short", 0x4a, new Format23x());
+ addOpcodeInfo(Opcode.APUT, "aput", 0x4b, new Format23x());
+ addOpcodeInfo(Opcode.APUT_WIDE, "aput-wide", 0x4c, new Format23x());
+ addOpcodeInfo(Opcode.APUT_OBJECT, "aput-object", 0x4d, new Format23x());
+ addOpcodeInfo(Opcode.APUT_BOOLEAN, "aput-boolean", 0x4e, new Format23x());
+ addOpcodeInfo(Opcode.APUT_BYTE, "aput-byte", 0x4f, new Format23x());
+ addOpcodeInfo(Opcode.APUT_CHAR, "aput-char", 0x50, new Format23x());
+ addOpcodeInfo(Opcode.APUT_SHORT, "aput-short", 0x51, new Format23x());
+ addOpcodeInfo(Opcode.IGET, "iget", 0x52, new Format22c());
+ addOpcodeInfo(Opcode.IGET_WIDE, "iget-wide", 0x53, new Format22c());
+ addOpcodeInfo(Opcode.IGET_OBJECT, "iget-object", 0x54, new Format22c());
+ addOpcodeInfo(Opcode.IGET_BOOLEAN, "iget-boolean", 0x55, new Format22c());
+ addOpcodeInfo(Opcode.IGET_BYTE, "iget-byte", 0x56, new Format22c());
+ addOpcodeInfo(Opcode.IGET_CHAR, "iget-char", 0x57, new Format22c());
+ addOpcodeInfo(Opcode.IGET_SHORT, "iget-short", 0x58, new Format22c());
+ addOpcodeInfo(Opcode.IPUT, "iput", 0x59, new Format22c());
+ addOpcodeInfo(Opcode.IPUT_WIDE, "iput-wide", 0x5a, new Format22c());
+ addOpcodeInfo(Opcode.IPUT_OBJECT, "iput-object", 0x5b, new Format22c());
+ addOpcodeInfo(Opcode.IPUT_BOOLEAN, "iput-boolean", 0x5c, new Format22c());
+ addOpcodeInfo(Opcode.IPUT_BYTE, "iput-byte", 0x5d, new Format22c());
+ addOpcodeInfo(Opcode.IPUT_CHAR, "iput-char", 0x5e, new Format22c());
+ addOpcodeInfo(Opcode.IPUT_SHORT, "iput-short", 0x5f, new Format22c());
+ addOpcodeInfo(Opcode.SGET, "sget", 0x60, new Format21c());
+ addOpcodeInfo(Opcode.SGET_WIDE, "sget-wide", 0x61, new Format21c());
+ addOpcodeInfo(Opcode.SGET_OBJECT, "sget-object", 0x62, new Format21c());
+ addOpcodeInfo(Opcode.SGET_BOOLEAN, "sget-boolean", 0x63, new Format21c());
+ addOpcodeInfo(Opcode.SGET_BYTE, "sget-byte", 0x64, new Format21c());
+ addOpcodeInfo(Opcode.SGET_CHAR, "sget-char", 0x65, new Format21c());
+ addOpcodeInfo(Opcode.SGET_SHORT, "sget-short", 0x66, new Format21c());
+ addOpcodeInfo(Opcode.SPUT, "sput", 0x67, new Format21c());
+ addOpcodeInfo(Opcode.SPUT_WIDE, "sput-wide", 0x68, new Format21c());
+ addOpcodeInfo(Opcode.SPUT_OBJECT, "sput-object", 0x69, new Format21c());
+ addOpcodeInfo(Opcode.SPUT_BOOLEAN, "sput-boolean", 0x6a, new Format21c());
+ addOpcodeInfo(Opcode.SPUT_BYTE, "sput-byte", 0x6b, new Format21c());
+ addOpcodeInfo(Opcode.SPUT_CHAR, "sput-char", 0x6c, new Format21c());
+ addOpcodeInfo(Opcode.SPUT_SHORT, "sput-short", 0x6d, new Format21c());
+ addOpcodeInfo(Opcode.INVOKE_VIRTUAL, "invoke-virtual", 0x6e, new Format35c());
+ addOpcodeInfo(Opcode.INVOKE_SUPER, "invoke-super", 0x6f, new Format35c());
+ addOpcodeInfo(Opcode.INVOKE_DIRECT, "invoke-direct", 0x70, new Format35c());
+ addOpcodeInfo(Opcode.INVOKE_STATIC, "invoke-static", 0x71, new Format35c());
+ addOpcodeInfo(Opcode.INVOKE_INTERFACE, "invoke-interface", 0x72, new Format35c());
+ addOpcodeInfo(Opcode.RETURN_VOID_BARRIER, "return-void-barrier", 0x73, new Format10x());
+ addOpcodeInfo(Opcode.INVOKE_VIRTUAL_RANGE, "invoke-virtual/range", 0x74, new Format3rc());
+ addOpcodeInfo(Opcode.INVOKE_SUPER_RANGE, "invoke-super/range", 0x75, new Format3rc());
+ addOpcodeInfo(Opcode.INVOKE_DIRECT_RANGE, "invoke-direct/range", 0x76, new Format3rc());
+ addOpcodeInfo(Opcode.INVOKE_STATIC_RANGE, "invoke-static/range", 0x77, new Format3rc());
+ addOpcodeInfo(Opcode.INVOKE_INTERFACE_RANGE, "invoke-interface/range",
+ 0x78, new Format3rc());
+ addOpcodeInfo(Opcode.UNUSED_79, "unused-79", 0x79, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_7A, "unused-7a", 0x7a, new Format10x());
+ addOpcodeInfo(Opcode.NEG_INT, "neg-int", 0x7b, new Format12x());
+ addOpcodeInfo(Opcode.NOT_INT, "not-int", 0x7c, new Format12x());
+ addOpcodeInfo(Opcode.NEG_LONG, "neg-long", 0x7d, new Format12x());
+ addOpcodeInfo(Opcode.NOT_LONG, "not-long", 0x7e, new Format12x());
+ addOpcodeInfo(Opcode.NEG_FLOAT, "neg-float", 0x7f, new Format12x());
+ addOpcodeInfo(Opcode.NEG_DOUBLE, "neg-double", 0x80, new Format12x());
+ addOpcodeInfo(Opcode.INT_TO_LONG, "int-to-long", 0x81, new Format12x());
+ addOpcodeInfo(Opcode.INT_TO_FLOAT, "int-to-float", 0x82, new Format12x());
+ addOpcodeInfo(Opcode.INT_TO_DOUBLE, "int-to-double", 0x83, new Format12x());
+ addOpcodeInfo(Opcode.LONG_TO_INT, "long-to-int", 0x84, new Format12x());
+ addOpcodeInfo(Opcode.LONG_TO_FLOAT, "long-to-float", 0x85, new Format12x());
+ addOpcodeInfo(Opcode.LONG_TO_DOUBLE, "long-to-double", 0x86, new Format12x());
+ addOpcodeInfo(Opcode.FLOAT_TO_INT, "float-to-int", 0x87, new Format12x());
+ addOpcodeInfo(Opcode.FLOAT_TO_LONG, "float-to-long", 0x88, new Format12x());
+ addOpcodeInfo(Opcode.FLOAT_TO_DOUBLE, "float-to-double", 0x89, new Format12x());
+ addOpcodeInfo(Opcode.DOUBLE_TO_INT, "double-to-int", 0x8a, new Format12x());
+ addOpcodeInfo(Opcode.DOUBLE_TO_LONG, "double-to-long", 0x8b, new Format12x());
+ addOpcodeInfo(Opcode.DOUBLE_TO_FLOAT, "double-to-float", 0x8c, new Format12x());
+ addOpcodeInfo(Opcode.INT_TO_BYTE, "int-to-byte", 0x8d, new Format12x());
+ addOpcodeInfo(Opcode.INT_TO_CHAR, "int-to-char", 0x8e, new Format12x());
+ addOpcodeInfo(Opcode.INT_TO_SHORT, "int-to-short", 0x8f, new Format12x());
+ addOpcodeInfo(Opcode.ADD_INT, "add-int", 0x90, new Format23x());
+ addOpcodeInfo(Opcode.SUB_INT, "sub-int", 0x91, new Format23x());
+ addOpcodeInfo(Opcode.MUL_INT, "mul-int", 0x92, new Format23x());
+ addOpcodeInfo(Opcode.DIV_INT, "div-int", 0x93, new Format23x());
+ addOpcodeInfo(Opcode.REM_INT, "rem-int", 0x94, new Format23x());
+ addOpcodeInfo(Opcode.AND_INT, "and-int", 0x95, new Format23x());
+ addOpcodeInfo(Opcode.OR_INT, "or-int", 0x96, new Format23x());
+ addOpcodeInfo(Opcode.XOR_INT, "xor-int", 0x97, new Format23x());
+ addOpcodeInfo(Opcode.SHL_INT, "shl-int", 0x98, new Format23x());
+ addOpcodeInfo(Opcode.SHR_INT, "shr-int", 0x99, new Format23x());
+ addOpcodeInfo(Opcode.USHR_INT, "ushr-int", 0x9a, new Format23x());
+ addOpcodeInfo(Opcode.ADD_LONG, "add-long", 0x9b, new Format23x());
+ addOpcodeInfo(Opcode.SUB_LONG, "sub-long", 0x9c, new Format23x());
+ addOpcodeInfo(Opcode.MUL_LONG, "mul-long", 0x9d, new Format23x());
+ addOpcodeInfo(Opcode.DIV_LONG, "div-long", 0x9e, new Format23x());
+ addOpcodeInfo(Opcode.REM_LONG, "rem-long", 0x9f, new Format23x());
+ addOpcodeInfo(Opcode.AND_LONG, "and-long", 0xa0, new Format23x());
+ addOpcodeInfo(Opcode.OR_LONG, "or-long", 0xa1, new Format23x());
+ addOpcodeInfo(Opcode.XOR_LONG, "xor-long", 0xa2, new Format23x());
+ addOpcodeInfo(Opcode.SHL_LONG, "shl-long", 0xa3, new Format23x());
+ addOpcodeInfo(Opcode.SHR_LONG, "shr-long", 0xa4, new Format23x());
+ addOpcodeInfo(Opcode.USHR_LONG, "ushr-long", 0xa5, new Format23x());
+ addOpcodeInfo(Opcode.ADD_FLOAT, "add-float", 0xa6, new Format23x());
+ addOpcodeInfo(Opcode.SUB_FLOAT, "sub-float", 0xa7, new Format23x());
+ addOpcodeInfo(Opcode.MUL_FLOAT, "mul-float", 0xa8, new Format23x());
+ addOpcodeInfo(Opcode.DIV_FLOAT, "div-float", 0xa9, new Format23x());
+ addOpcodeInfo(Opcode.REM_FLOAT, "rem-float", 0xaa, new Format23x());
+ addOpcodeInfo(Opcode.ADD_DOUBLE, "add-double", 0xab, new Format23x());
+ addOpcodeInfo(Opcode.SUB_DOUBLE, "sub-double", 0xac, new Format23x());
+ addOpcodeInfo(Opcode.MUL_DOUBLE, "mul-double", 0xad, new Format23x());
+ addOpcodeInfo(Opcode.DIV_DOUBLE, "div-double", 0xae, new Format23x());
+ addOpcodeInfo(Opcode.REM_DOUBLE, "rem-double", 0xaf, new Format23x());
+ addOpcodeInfo(Opcode.ADD_INT_2ADDR, "add-int/2addr", 0xb0, new Format12x());
+ addOpcodeInfo(Opcode.SUB_INT_2ADDR, "sub-int/2addr", 0xb1, new Format12x());
+ addOpcodeInfo(Opcode.MUL_INT_2ADDR, "mul-int/2addr", 0xb2, new Format12x());
+ addOpcodeInfo(Opcode.DIV_INT_2ADDR, "div-int/2addr", 0xb3, new Format12x());
+ addOpcodeInfo(Opcode.REM_INT_2ADDR, "rem-int/2addr", 0xb4, new Format12x());
+ addOpcodeInfo(Opcode.AND_INT_2ADDR, "and-int/2addr", 0xb5, new Format12x());
+ addOpcodeInfo(Opcode.OR_INT_2ADDR, "or-int/2addr", 0xb6, new Format12x());
+ addOpcodeInfo(Opcode.XOR_INT_2ADDR, "xor-int/2addr", 0xb7, new Format12x());
+ addOpcodeInfo(Opcode.SHL_INT_2ADDR, "shl-int/2addr", 0xb8, new Format12x());
+ addOpcodeInfo(Opcode.SHR_INT_2ADDR, "shr-int/2addr", 0xb9, new Format12x());
+ addOpcodeInfo(Opcode.USHR_INT_2ADDR, "ushr-int/2addr", 0xba, new Format12x());
+ addOpcodeInfo(Opcode.ADD_LONG_2ADDR, "add-long/2addr", 0xbb, new Format12x());
+ addOpcodeInfo(Opcode.SUB_LONG_2ADDR, "sub-long/2addr", 0xbc, new Format12x());
+ addOpcodeInfo(Opcode.MUL_LONG_2ADDR, "mul-long/2addr", 0xbd, new Format12x());
+ addOpcodeInfo(Opcode.DIV_LONG_2ADDR, "div-long/2addr", 0xbe, new Format12x());
+ addOpcodeInfo(Opcode.REM_LONG_2ADDR, "rem-long/2addr", 0xbf, new Format12x());
+ addOpcodeInfo(Opcode.AND_LONG_2ADDR, "and-long/2addr", 0xc0, new Format12x());
+ addOpcodeInfo(Opcode.OR_LONG_2ADDR, "or-long/2addr", 0xc1, new Format12x());
+ addOpcodeInfo(Opcode.XOR_LONG_2ADDR, "xor-long/2addr", 0xc2, new Format12x());
+ addOpcodeInfo(Opcode.SHL_LONG_2ADDR, "shl-long/2addr", 0xc3, new Format12x());
+ addOpcodeInfo(Opcode.SHR_LONG_2ADDR, "shr-long/2addr", 0xc4, new Format12x());
+ addOpcodeInfo(Opcode.USHR_LONG_2ADDR, "ushr-long/2addr", 0xc5, new Format12x());
+ addOpcodeInfo(Opcode.ADD_FLOAT_2ADDR, "add-float/2addr", 0xc6, new Format12x());
+ addOpcodeInfo(Opcode.SUB_FLOAT_2ADDR, "sub-float/2addr", 0xc7, new Format12x());
+ addOpcodeInfo(Opcode.MUL_FLOAT_2ADDR, "mul-float/2addr", 0xc8, new Format12x());
+ addOpcodeInfo(Opcode.DIV_FLOAT_2ADDR, "div-float/2addr", 0xc9, new Format12x());
+ addOpcodeInfo(Opcode.REM_FLOAT_2ADDR, "rem-float/2addr", 0xca, new Format12x());
+ addOpcodeInfo(Opcode.ADD_DOUBLE_2ADDR, "add-double/2addr", 0xcb, new Format12x());
+ addOpcodeInfo(Opcode.SUB_DOUBLE_2ADDR, "sub-double/2addr", 0xcc, new Format12x());
+ addOpcodeInfo(Opcode.MUL_DOUBLE_2ADDR, "mul-double/2addr", 0xcd, new Format12x());
+ addOpcodeInfo(Opcode.DIV_DOUBLE_2ADDR, "div-double/2addr", 0xce, new Format12x());
+ addOpcodeInfo(Opcode.REM_DOUBLE_2ADDR, "rem-double/2addr", 0xcf, new Format12x());
+ addOpcodeInfo(Opcode.ADD_INT_LIT16, "add-int/lit16", 0xd0, new Format22s());
+ addOpcodeInfo(Opcode.RSUB_INT, "rsub-int", 0xd1, new Format22s());
+ addOpcodeInfo(Opcode.MUL_INT_LIT16, "mul-int/lit16", 0xd2, new Format22s());
+ addOpcodeInfo(Opcode.DIV_INT_LIT16, "div-int/lit16", 0xd3, new Format22s());
+ addOpcodeInfo(Opcode.REM_INT_LIT16, "rem-int/lit16", 0xd4, new Format22s());
+ addOpcodeInfo(Opcode.AND_INT_LIT16, "and-int/lit16", 0xd5, new Format22s());
+ addOpcodeInfo(Opcode.OR_INT_LIT16, "or-int/lit16", 0xd6, new Format22s());
+ addOpcodeInfo(Opcode.XOR_INT_LIT16, "xor-int/lit16", 0xd7, new Format22s());
+ addOpcodeInfo(Opcode.ADD_INT_LIT8, "add-int/lit8", 0xd8, new Format22b());
+ addOpcodeInfo(Opcode.RSUB_INT_LIT8, "rsub-int/lit8", 0xd9, new Format22b());
+ addOpcodeInfo(Opcode.MUL_INT_LIT8, "mul-int/lit8", 0xda, new Format22b());
+ addOpcodeInfo(Opcode.DIV_INT_LIT8, "div-int/lit8", 0xdb, new Format22b());
+ addOpcodeInfo(Opcode.REM_INT_LIT8, "rem-int/lit8", 0xdc, new Format22b());
+ addOpcodeInfo(Opcode.AND_INT_LIT8, "and-int/lit8", 0xdd, new Format22b());
+ addOpcodeInfo(Opcode.OR_INT_LIT8, "or-int/lit8", 0xde, new Format22b());
+ addOpcodeInfo(Opcode.XOR_INT_LIT8, "xor-int/lit8", 0xdf, new Format22b());
+ addOpcodeInfo(Opcode.SHL_INT_LIT8, "shl-int/lit8", 0xe0, new Format22b());
+ addOpcodeInfo(Opcode.SHR_INT_LIT8, "shr-int/lit8", 0xe1, new Format22b());
+ addOpcodeInfo(Opcode.USHR_INT_LIT8, "ushr-int/lit8", 0xe2, new Format22b());
+ addOpcodeInfo(Opcode.IGET_QUICK, "+iget-quick", 0xe3, new Format22c());
+ addOpcodeInfo(Opcode.IGET_WIDE_QUICK, "+iget-wide-quick", 0xe4, new Format22c());
+ addOpcodeInfo(Opcode.IGET_OBJECT_QUICK, "+iget-object-quick", 0xe5, new Format22c());
+ addOpcodeInfo(Opcode.IPUT_QUICK, "+iput-quick", 0xe6, new Format22c());
+ addOpcodeInfo(Opcode.IPUT_WIDE_QUICK, "+iput-wide-quick", 0xe7, new Format22c());
+ addOpcodeInfo(Opcode.IPUT_OBJECT_QUICK, "+iput-object-quick", 0xe8, new Format22c());
+ addOpcodeInfo(Opcode.INVOKE_VIRTUAL_QUICK, "+invoke-virtual-quick", 0xe9, new Format35c());
+ addOpcodeInfo(Opcode.INVOKE_VIRTUAL_QUICK_RANGE, "+invoke-virtual-quick/range",
+ 0xea, new Format3rc());
+ addOpcodeInfo(Opcode.IPUT_BOOLEAN_QUICK, "+iput-boolean-quick", 0xeb, new Format22c());
+ addOpcodeInfo(Opcode.IPUT_BYTE_QUICK, "+iput-byte-quick", 0xec, new Format22c());
+ addOpcodeInfo(Opcode.IPUT_CHAR_QUICK, "+iput-char-quick", 0xed, new Format22c());
+ addOpcodeInfo(Opcode.IPUT_SHORT_QUICK, "+iput-short-quick", 0xee, new Format22c());
+ addOpcodeInfo(Opcode.UNUSED_EF, "unused-ef", 0xef, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_F0, "unused-f0", 0xf0, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_F1, "unused-f1", 0xf1, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_F2, "unused-f2", 0xf2, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_F3, "unused-f3", 0xf3, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_F4, "unused-f4", 0xf4, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_F5, "unused-f5", 0xf5, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_F6, "unused-f6", 0xf6, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_F7, "unused-f7", 0xf7, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_F8, "unused-f8", 0xf8, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_F9, "unused-f9", 0xf9, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_FA, "unused-fa", 0xfa, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_FB, "unused-fb", 0xfb, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_FC, "unused-fc", 0xfc, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_FD, "unused-fd", 0xfd, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_FE, "unused-fe", 0xfe, new Format10x());
+ addOpcodeInfo(Opcode.UNUSED_FF, "unused-ff", 0xff, new Format10x());
+ if (opcode_map_by_int.size() != 256) {
+ Log.errorAndQuit("Incorrect number of bytecodes defined.");
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/MapItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/MapItem.java
new file mode 100644
index 0000000..4ca2463
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/MapItem.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class MapItem implements RawDexObject {
+ public static final int TYPE_HEADER_ITEM = 0x0;
+ public static final int TYPE_STRING_ID_ITEM = 0x1;
+ public static final int TYPE_TYPE_ID_ITEM = 0x2;
+ public static final int TYPE_PROTO_ID_ITEM = 0x3;
+ public static final int TYPE_FIELD_ID_ITEM = 0x4;
+ public static final int TYPE_METHOD_ID_ITEM = 0x5;
+ public static final int TYPE_CLASS_DEF_ITEM = 0x6;
+ public static final int TYPE_MAP_LIST = 0x1000;
+ public static final int TYPE_TYPE_LIST = 0x1001;
+ public static final int TYPE_ANNOTATION_SET_REF_LIST = 0x1002;
+ public static final int TYPE_ANNOTATION_SET_ITEM = 0x1003;
+ public static final int TYPE_CLASS_DATA_ITEM = 0x2000;
+ public static final int TYPE_CODE_ITEM = 0x2001;
+ public static final int TYPE_STRING_DATA_ITEM = 0x2002;
+ public static final int TYPE_DEBUG_INFO_ITEM = 0x2003;
+ public static final int TYPE_ANNOTATION_ITEM = 0x2004;
+ public static final int TYPE_ENCODED_ARRAY_ITEM = 0x2005;
+ public static final int TYPE_ANNOTATIONS_DIRECTORY_ITEM = 0x2006;
+
+ public short type;
+ public int size;
+ public Offset offset;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ type = file.readUShort();
+ file.readUShort(); // Unused padding.
+ size = file.readUInt();
+ if (type == TYPE_HEADER_ITEM) {
+ offset = file.getOffsetTracker().getNewHeaderOffset(file.readUInt());
+ } else {
+ offset = file.getOffsetTracker().getNewOffset(file.readUInt());
+ }
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.writeUShort(type);
+ file.writeUShort((short) 0); // Unused padding.
+ file.writeUInt(size);
+ file.getOffsetTracker().tryToWriteOffset(offset, file, false /* ULEB128 */);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ // Do nothing.
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/MapList.java b/tools/dexfuzz/src/dexfuzz/rawdex/MapList.java
new file mode 100644
index 0000000..080b5a4
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/MapList.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import dexfuzz.Log;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MapList implements RawDexObject {
+
+ private RawDexFile rawDexFile;
+
+ public int size;
+ public List<MapItem> mapItems;
+
+ public MapList(RawDexFile rawDexFile) {
+ this.rawDexFile = rawDexFile;
+ }
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ // Find the map list.
+ file.seek(rawDexFile.header.mapOff.getOriginalOffset());
+
+ file.getOffsetTracker().getNewOffsettable(file, this);
+
+ // Get the number of entries.
+ size = file.readUInt();
+
+ // Allocate and populate the array.
+ mapItems = new ArrayList<MapItem>(size);
+ for (int i = 0; i < size; i++) {
+ MapItem mapItem = new MapItem();
+ mapItems.add(mapItem);
+ mapItem.read(file);
+ }
+
+ file.getOffsetTracker().rememberPointAfterMapList();
+
+ // NB: We track the current index into the MapList, so when we encounter the DebugInfoItem
+ // MapItem, we know how to find the next MapItem, so we know how large the DebugInfo
+ // area is, so we can copy it as a blob.
+ int mapItemIdx = 0;
+
+ // Iterate through the list, and create all the other data structures.
+ for (MapItem mapItem : mapItems) {
+ file.seek(mapItem.offset.getOriginalOffset());
+ switch (mapItem.type) {
+ case MapItem.TYPE_HEADER_ITEM:
+ // Already read it; skip.
+ break;
+ case MapItem.TYPE_STRING_ID_ITEM:
+ for (int i = 0; i < mapItem.size; i++) {
+ StringIdItem newStringId = new StringIdItem();
+ rawDexFile.stringIds.add(newStringId);
+ newStringId.read(file);
+ }
+ break;
+ case MapItem.TYPE_TYPE_ID_ITEM:
+ for (int i = 0; i < mapItem.size; i++) {
+ TypeIdItem newTypeId = new TypeIdItem();
+ rawDexFile.typeIds.add(newTypeId);
+ newTypeId.read(file);
+ }
+ break;
+ case MapItem.TYPE_PROTO_ID_ITEM:
+ for (int i = 0; i < mapItem.size; i++) {
+ ProtoIdItem newProtoId = new ProtoIdItem();
+ rawDexFile.protoIds.add(newProtoId);
+ newProtoId.read(file);
+ }
+ break;
+ case MapItem.TYPE_FIELD_ID_ITEM:
+ for (int i = 0; i < mapItem.size; i++) {
+ FieldIdItem newFieldId = new FieldIdItem();
+ rawDexFile.fieldIds.add(newFieldId);
+ newFieldId.read(file);
+ }
+ break;
+ case MapItem.TYPE_METHOD_ID_ITEM:
+ for (int i = 0; i < mapItem.size; i++) {
+ MethodIdItem newMethodId = new MethodIdItem();
+ rawDexFile.methodIds.add(newMethodId);
+ newMethodId.read(file);
+ }
+ break;
+ case MapItem.TYPE_CLASS_DEF_ITEM:
+ for (int i = 0; i < mapItem.size; i++) {
+ ClassDefItem newClassDef = new ClassDefItem();
+ rawDexFile.classDefs.add(newClassDef);
+ newClassDef.read(file);
+ }
+ break;
+ case MapItem.TYPE_MAP_LIST:
+ // Already read it; skip.
+ break;
+ case MapItem.TYPE_TYPE_LIST:
+ rawDexFile.typeLists = new ArrayList<TypeList>(mapItem.size);
+ for (int i = 0; i < mapItem.size; i++) {
+ TypeList newTypeList = new TypeList();
+ rawDexFile.typeLists.add(newTypeList);
+ newTypeList.read(file);
+ }
+ break;
+ case MapItem.TYPE_ANNOTATION_SET_REF_LIST:
+ rawDexFile.annotationSetRefLists =
+ new ArrayList<AnnotationSetRefList>(mapItem.size);
+ for (int i = 0; i < mapItem.size; i++) {
+ AnnotationSetRefList newAnnotationSetRefList = new AnnotationSetRefList();
+ rawDexFile.annotationSetRefLists.add(newAnnotationSetRefList);
+ newAnnotationSetRefList.read(file);
+ }
+ break;
+ case MapItem.TYPE_ANNOTATION_SET_ITEM:
+ rawDexFile.annotationSetItems = new ArrayList<AnnotationSetItem>(mapItem.size);
+ for (int i = 0; i < mapItem.size; i++) {
+ AnnotationSetItem newAnnotationSetItem = new AnnotationSetItem();
+ rawDexFile.annotationSetItems.add(newAnnotationSetItem);
+ newAnnotationSetItem.read(file);
+ }
+ break;
+ case MapItem.TYPE_CLASS_DATA_ITEM:
+ rawDexFile.classDatas = new ArrayList<ClassDataItem>(mapItem.size);
+ for (int i = 0; i < mapItem.size; i++) {
+ ClassDataItem newClassData = new ClassDataItem();
+ rawDexFile.classDatas.add(newClassData);
+ newClassData.read(file);
+ }
+ break;
+ case MapItem.TYPE_CODE_ITEM:
+ rawDexFile.codeItems = new ArrayList<CodeItem>(mapItem.size);
+ for (int i = 0; i < mapItem.size; i++) {
+ CodeItem newCodeItem = new CodeItem();
+ rawDexFile.codeItems.add(newCodeItem);
+ newCodeItem.read(file);
+ }
+ break;
+ case MapItem.TYPE_STRING_DATA_ITEM:
+ rawDexFile.stringDatas = new ArrayList<StringDataItem>(mapItem.size);
+ for (int i = 0; i < mapItem.size; i++) {
+ StringDataItem newStringData = new StringDataItem();
+ rawDexFile.stringDatas.add(newStringData);
+ newStringData.read(file);
+ }
+ break;
+ case MapItem.TYPE_DEBUG_INFO_ITEM:
+ {
+ // We aren't interested in updating the debug data, so just read it as a blob.
+ int start = mapItem.offset.getOriginalOffset();
+ int end = mapItems.get(mapItemIdx + 1).offset.getOriginalOffset();
+ int size = end - start;
+ rawDexFile.debugInfoItem = new DebugInfoItem(size);
+ rawDexFile.debugInfoItem.read(file);
+ break;
+ }
+ case MapItem.TYPE_ANNOTATION_ITEM:
+ rawDexFile.annotationItems = new ArrayList<AnnotationItem>(mapItem.size);
+ for (int i = 0; i < mapItem.size; i++) {
+ AnnotationItem newAnnotationItem = new AnnotationItem();
+ rawDexFile.annotationItems.add(newAnnotationItem);
+ newAnnotationItem.read(file);
+ }
+ break;
+ case MapItem.TYPE_ENCODED_ARRAY_ITEM:
+ rawDexFile.encodedArrayItems = new ArrayList<EncodedArrayItem>(mapItem.size);
+ for (int i = 0; i < mapItem.size; i++) {
+ EncodedArrayItem newEncodedArrayItem = new EncodedArrayItem();
+ rawDexFile.encodedArrayItems.add(newEncodedArrayItem);
+ newEncodedArrayItem.read(file);
+ }
+ break;
+ case MapItem.TYPE_ANNOTATIONS_DIRECTORY_ITEM:
+ rawDexFile.annotationsDirectoryItems =
+ new ArrayList<AnnotationsDirectoryItem>(mapItem.size);
+ for (int i = 0; i < mapItem.size; i++) {
+ AnnotationsDirectoryItem newAnnotationsDirectoryItem = new AnnotationsDirectoryItem();
+ rawDexFile.annotationsDirectoryItems.add(newAnnotationsDirectoryItem);
+ newAnnotationsDirectoryItem.read(file);
+ }
+ break;
+ default:
+ Log.errorAndQuit("Encountered unknown map item when reading map item list.");
+ }
+ mapItemIdx++;
+ }
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.alignForwards(4);
+ file.getOffsetTracker().updatePositionOfNextOffsettable(file);
+ file.writeUInt(mapItems.size());
+ for (MapItem mapItem : mapItems) {
+ mapItem.write(file);
+ }
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ // Do nothing.
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/MethodAnnotation.java b/tools/dexfuzz/src/dexfuzz/rawdex/MethodAnnotation.java
new file mode 100644
index 0000000..1a24fb6
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/MethodAnnotation.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class MethodAnnotation implements RawDexObject {
+ public int methodIdx;
+ public Offset annotationsOff;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ methodIdx = file.readUInt();
+ annotationsOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.writeUInt(methodIdx);
+ file.getOffsetTracker().tryToWriteOffset(annotationsOff, file, false /* ULEB128 */);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ if (kind == IndexUpdateKind.METHOD_ID && methodIdx >= insertedIdx) {
+ methodIdx++;
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/MethodIdItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/MethodIdItem.java
new file mode 100644
index 0000000..12d0187
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/MethodIdItem.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class MethodIdItem implements RawDexObject {
+ public short classIdx;
+ public short protoIdx;
+ public int nameIdx;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().getNewOffsettable(file, this);
+ classIdx = file.readUShort();
+ protoIdx = file.readUShort();
+ nameIdx = file.readUInt();
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().updatePositionOfNextOffsettable(file);
+ file.writeUShort(classIdx);
+ file.writeUShort(protoIdx);
+ file.writeUInt(nameIdx);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ if (kind == IndexUpdateKind.TYPE_ID && classIdx >= insertedIdx) {
+ classIdx++;
+ }
+ if (kind == IndexUpdateKind.PROTO_ID && protoIdx >= insertedIdx) {
+ protoIdx++;
+ }
+ if (kind == IndexUpdateKind.STRING_ID && nameIdx >= insertedIdx) {
+ nameIdx++;
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/Offset.java b/tools/dexfuzz/src/dexfuzz/rawdex/Offset.java
new file mode 100644
index 0000000..b6718e3
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/Offset.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import dexfuzz.Log;
+
+public class Offset {
+ /**
+ * The absolute value of this offset as it was originally read.
+ */
+ private int originalOffset;
+
+ /**
+ * The Offsettable that this Offset points to.
+ */
+ private Offsettable offsettable;
+
+ /**
+ * The location of this Offset in the new file, ONLY SET IF the Offset
+ * couldn't be written because what it points to hasn't been written
+ * yet.
+ */
+ private int outputLocation;
+
+ /**
+ * Was the output location for this Offset set?.
+ */
+ private boolean outputLocationSet;
+
+ /**
+ * Does this Offset need to be written out using ULEB128?.
+ */
+ private boolean useUleb128;
+
+ /**
+ * Was this Offset created after reading, during mutation?.
+ */
+ private boolean isNewOffset;
+
+ /**
+ * Only one Offset should have this flag set, the MapItem that points
+ * to the HeaderItem.
+ */
+ private boolean pointsAtHeader;
+
+ /**
+ * If an Offset pointed at 0 (because it is not actually a valid Offset),
+ * and it's not pointing at the header, then this is set.
+ */
+ private boolean pointsAtNull;
+
+ public Offset(boolean header) {
+ pointsAtHeader = header;
+ }
+
+ public RawDexObject getPointedToItem() {
+ return offsettable.getItem();
+ }
+
+ public boolean pointsToSomething() {
+ return offsettable != null;
+ }
+
+ public boolean pointsAtNull() {
+ return pointsAtNull;
+ }
+
+ public boolean pointsAtHeader() {
+ return pointsAtHeader;
+ }
+
+ /**
+ * Returns true if this Offset points at the provided RawDexObject.
+ */
+ public boolean pointsToThisItem(RawDexObject thisItem) {
+ if (!pointsToSomething()) {
+ return false;
+ }
+ return (offsettable.getItem().equals(thisItem));
+ }
+
+ /**
+ * Returns true if this Offset points at the provided Offsettable.
+ */
+ public boolean pointsToThisOffsettable(Offsettable thisOffsettable) {
+ if (!pointsToSomething()) {
+ return false;
+ }
+ return (offsettable.equals(thisOffsettable));
+ }
+
+ /**
+ * Makes this Offset point at a new Offsettable.
+ */
+ public void pointTo(Offsettable offsettableItem) {
+ if (offsettable != null) {
+ Log.debug("Updating what an Offset points to...");
+ }
+ offsettable = offsettableItem;
+ }
+
+ /**
+ * Call this to make an Offset that pointed at null before now point at something.
+ * An Offset may have previously pointed at null before...
+ * Example: if there are no fields referred to in a DEX file, then header.field_ids_off
+ * will point at null. We distinguish when Offsets point at null, and are not pointing
+ * at the header (only the header MapItem should do this) with a flag. Therefore, this
+ * method is needed to indicate that this Offset now points at something.
+ */
+ public void unsetNullAndPointTo(Offsettable offsettableItem) {
+ pointsAtNull = false;
+ if (offsettable != null) {
+ Log.debug("Updating what an Offset points to...");
+ }
+ offsettable = offsettableItem;
+ }
+
+ public void pointToNew(Offsettable offsettableItem) {
+ offsettable = offsettableItem;
+ isNewOffset = true;
+ }
+
+ public int getNewPositionOfItem() {
+ return offsettable.getNewPosition();
+ }
+
+ public boolean usesUleb128() {
+ return useUleb128;
+ }
+
+ /**
+ * Mark this Offset as using the ULEB128 encoding.
+ */
+ public void setUsesUleb128() {
+ if (useUleb128) {
+ throw new Error("Offset is already marked as using ULEB128!");
+ }
+ useUleb128 = true;
+ }
+
+ public boolean isNewOffset() {
+ return isNewOffset;
+ }
+
+ public void setPointsAtNull() {
+ pointsAtNull = true;
+ }
+
+ public void setOutputLocation(int loc) {
+ outputLocation = loc;
+ outputLocationSet = true;
+ }
+
+ /**
+ * Get the location in the output DEX file where this offset has been written.
+ * (This is used when patching Offsets when the Offsettable position was not
+ * known at the time of writing out the Offset.)
+ */
+ public int getOutputLocation() {
+ if (!outputLocationSet) {
+ throw new Error("Output location was not set yet!");
+ }
+ return outputLocation;
+ }
+
+ public void setOriginalOffset(int offset) {
+ originalOffset = offset;
+ }
+
+ public int getOriginalOffset() {
+ return originalOffset;
+ }
+
+ public boolean readyForWriting() {
+ return offsettable.readyForFinalOffsetToBeWritten();
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/OffsetTracker.java b/tools/dexfuzz/src/dexfuzz/rawdex/OffsetTracker.java
new file mode 100644
index 0000000..34d609a
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/OffsetTracker.java
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import dexfuzz.Log;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class allows the recording of both Offsettable items (that is, items that can be
+ * referred to by an offset somewhere else in the file - RawDexObjects) and Offsets.
+ * The idea in a nutshell is that for every Offsettable item we read, we remember
+ * its original position in the file using a map, and the order in which the Offsettables were
+ * written out. We also remember every Offset we read in, and its value. Then, after reading
+ * the whole file, we use the map to find the Offsettable it pointed at.
+ * Then, as we write out the file, for every Offsettable we write out, we record its new position,
+ * using the order we collected earlier. For every Offset we write out, we look at its Offsettable
+ * to see where it was written. If it hasn't been written yet, then we write out a blank value
+ * for the time being, remember where that blank value was written, and put the Offset into a
+ * table for patching once everything has been written out.
+ * There are some variables (index_after_map_list, restore_point) used for remembering certain
+ * points to jump forward and back to, because we cannot read and write the file out in exactly
+ * the same order.
+ * TODO: Perhaps it makes more sense to just reorder the offsettable_table once it's been read,
+ * in preparation for the order in which the file is written out?
+ * Finally, we provide methods for adding new Offsettable items into the right place in the order
+ * table.
+ */
+public class OffsetTracker {
+ /**
+ * A map from the original offset in the input DEX file to
+ * the Offsettable it points to. (That Offsettable will contain
+ * the actual item, and later on the new offset for the item when
+ * the item is written out.
+ */
+ private Map<Integer, Offsettable> offsettableMap;
+
+ /**
+ * A table of all Offsettables. We need to ensure we write out
+ * all items in the same order we read them in, to make sure we update
+ * the Offsettable.new_position field with the correct value wrt to
+ * the original_position field.
+ */
+ private List<Offsettable> offsettableTable;
+
+ /**
+ * A table of all offsets that is populated as we read in the DEX file.
+ * As the end, we find the correct Offsettable for the Offset in the above
+ * map, and associate them.
+ */
+ private List<Offset> needsAssociationTable;
+
+ /**
+ * A table of all offsets that we could not write out an updated offset for
+ * as we write out a DEX file. Will be checked after writing is complete,
+ * to allow specific patching of each offset's location as at that point
+ * all Offsettables will have been updated with their new position.
+ */
+ private List<Offset> needsUpdateTable;
+
+ /**
+ * Tracks how far we are through the offsettable_table as we write out the file.
+ */
+ private int offsettableTableIdx;
+
+ /**
+ * Because we write in a slightly different order to how we read
+ * (why? to read, we read the header, then the map list, and then use the map
+ * list to read everything else.
+ * when we write, we write the header, and then we cannot write the map list
+ * because we don't know where it will go yet, so we write everything else first)
+ * so: we remember the position in the offsettable_table after we read the map list,
+ * so we can skip there after we write out the header.
+ */
+ private int indexAfterMapList;
+
+ /**
+ * Related to index_after_map_list, this is the index we save when we're jumping back to
+ * write the map list.
+ */
+ private int restorePoint;
+
+ /**
+ * Create a new OffsetTracker. Should persist between parsing a DEX file, and outputting
+ * the mutated DEX file.
+ */
+ public OffsetTracker() {
+ offsettableMap = new HashMap<Integer,Offsettable>();
+ offsettableTable = new ArrayList<Offsettable>();
+ needsAssociationTable = new ArrayList<Offset>();
+ needsUpdateTable = new ArrayList<Offset>();
+ }
+
+ /**
+ * Lookup an Item by the offset it had in the input DEX file.
+ * @param offset The offset in the input DEX file.
+ * @return The corresponding Item.
+ */
+ public RawDexObject getItemByOffset(int offset) {
+ return offsettableMap.get(offset).getItem();
+ }
+
+ /**
+ * As Items are read in, they call this function once they have word-aligned the file pointer,
+ * to record their position and themselves into an Offsettable object, that will be tracked.
+ * @param file Used for recording position into the new Offsettable.
+ * @param item Used for recording the relevant Item into the new Offsettable.
+ */
+ public void getNewOffsettable(DexRandomAccessFile file, RawDexObject item) throws IOException {
+ Offsettable offsettable = new Offsettable(item, false);
+ offsettable.setOriginalPosition((int) file.getFilePointer());
+ offsettableMap.put(offsettable.getOriginalPosition(), offsettable);
+ offsettableTable.add(offsettable);
+ }
+
+ /**
+ * As Items read in Offsets, they call this function with the offset they originally
+ * read from the file, to allow later association with an Offsettable.
+ * @param originalOffset The original offset read from the input DEX file.
+ * @return An Offset that will later be associated with an Offsettable.
+ */
+ public Offset getNewOffset(int originalOffset) throws IOException {
+ Offset offset = new Offset(false);
+ offset.setOriginalOffset(originalOffset);
+ needsAssociationTable.add(offset);
+ return offset;
+ }
+
+ /**
+ * Only MapItem should call this method, when the MapItem that points to the header
+ * is read.
+ */
+ public Offset getNewHeaderOffset(int originalOffset) throws IOException {
+ Offset offset = new Offset(true);
+ offset.setOriginalOffset(originalOffset);
+ needsAssociationTable.add(offset);
+ return offset;
+ }
+
+ /**
+ * Call this after reading, to associate Offsets with Offsettables.
+ */
+ public void associateOffsets() {
+ for (Offset offset : needsAssociationTable) {
+ if (offset.getOriginalOffset() == 0 && !(offset.pointsAtHeader())) {
+ offset.setPointsAtNull();
+ } else {
+ offset.pointTo(offsettableMap.get(offset.getOriginalOffset()));
+ if (!offset.pointsToSomething()) {
+ Log.error(String.format("Couldn't find original offset 0x%x!",
+ offset.getOriginalOffset()));
+ }
+ }
+ }
+ needsAssociationTable.clear();
+ }
+
+ /**
+ * As Items are written out into the output DEX file, this function is called
+ * to update the next Offsettable with the file pointer's current position.
+ * This should allow the tracking of new offset locations.
+ * This also requires that reading and writing of all items happens in the same order
+ * (with the exception of the map list, see above)
+ * @param file Used for recording the new position.
+ */
+ public void updatePositionOfNextOffsettable(DexRandomAccessFile file) throws IOException {
+ if (offsettableTableIdx == offsettableTable.size()) {
+ Log.errorAndQuit("Not all created Offsettable items have been added to the "
+ + "Offsettable Table!");
+ }
+ Offsettable offsettable = offsettableTable.get(offsettableTableIdx);
+ offsettable.setNewPosition((int) file.getFilePointer());
+ offsettableTableIdx++;
+ }
+
+ /**
+ * As Items are written out, any writing out of an offset must call this function, passing
+ * in the relevant offset. This function will write out the offset, if the associated
+ * Offsettable has been updated with its new position, or else will write out a null value, and
+ * the Offset will be stored for writing after all Items have been written, and all
+ * Offsettables MUST have been updated.
+ * @param offset The offset received from getNewOffset().
+ * @param file Used for writing out to the file.
+ * @param useUleb128 Whether or not the offset should be written in UINT or ULEB128 form.
+ */
+ public void tryToWriteOffset(Offset offset, DexRandomAccessFile file, boolean useUleb128)
+ throws IOException {
+ if (!offset.isNewOffset() && (!offset.pointsToSomething())) {
+ if (useUleb128) {
+ file.writeUleb128(0);
+ } else {
+ file.writeUInt(0);
+ }
+ return;
+ }
+
+ if (offset.readyForWriting()) {
+ if (useUleb128) {
+ file.writeUleb128(offset.getNewPositionOfItem());
+ } else {
+ file.writeUInt(offset.getNewPositionOfItem());
+ }
+ } else {
+ offset.setOutputLocation((int) file.getFilePointer());
+ if (useUleb128) {
+ file.writeLargestUleb128(offset.getOriginalOffset());
+ offset.setUsesUleb128();
+ } else {
+ file.writeUInt(offset.getOriginalOffset());
+ }
+ needsUpdateTable.add(offset);
+ }
+ }
+
+ /**
+ * This is called after all writing has finished, to write out any Offsets
+ * that could not be written out during the original writing phase, because their
+ * associated Offsettables hadn't had their new positions calculated yet.
+ * @param file Used for writing out to the file.
+ */
+ public void updateOffsets(DexRandomAccessFile file) throws IOException {
+ if (offsettableTableIdx != offsettableTable.size()) {
+ Log.errorAndQuit("Being asked to update dangling offsets but the "
+ + "correct number of offsettables has not been written out!");
+ }
+ for (Offset offset : needsUpdateTable) {
+ file.seek(offset.getOutputLocation());
+ if (offset.usesUleb128()) {
+ file.writeLargestUleb128(offset.getNewPositionOfItem());
+ } else {
+ file.writeUInt(offset.getNewPositionOfItem());
+ }
+ }
+ needsUpdateTable.clear();
+ }
+
+ /**
+ * Called after writing out the header, to skip to after the map list.
+ */
+ public void skipToAfterMapList() {
+ offsettableTableIdx = indexAfterMapList;
+ }
+
+ /**
+ * Called once the map list needs to be written out, to set the
+ * offsettable table index back to the right place.
+ */
+ public void goBackToMapList() {
+ restorePoint = offsettableTableIdx;
+ offsettableTableIdx = (indexAfterMapList - 1);
+ }
+
+ /**
+ * Called once the map list has been written out, to set the
+ * offsettable table index back to where it was before.
+ */
+ public void goBackToPreviousPoint() {
+ if (offsettableTableIdx != indexAfterMapList) {
+ Log.errorAndQuit("Being asked to go to the point before the MapList was written out,"
+ + " but we're not in the right place.");
+ }
+ offsettableTableIdx = restorePoint;
+ }
+
+ /**
+ * Called after reading in the map list, to remember the point to be skipped
+ * to later.
+ */
+ public void rememberPointAfterMapList() {
+ indexAfterMapList = offsettableTable.size();
+ }
+
+ private void updateHeaderOffsetIfValid(Offset offset, Offsettable previousFirst,
+ Offsettable newFirst, String offsetName) {
+ if (offset.pointsToThisOffsettable(previousFirst)) {
+ offset.pointToNew(newFirst);
+ } else {
+ Log.errorAndQuit("Header " + offsetName + " offset not pointing at first element?");
+ }
+ }
+
+ private void addTypeListsToMapFile(RawDexFile rawDexFile, Offsettable typeListOffsettable) {
+ // Create a MapItem for the TypeLists
+ MapItem typeListMapItem = new MapItem();
+ typeListMapItem.offset = new Offset(false);
+ typeListMapItem.offset.pointToNew(typeListOffsettable);
+ typeListMapItem.type = MapItem.TYPE_TYPE_LIST;
+ typeListMapItem.size = 1;
+
+ // Insert into the MapList.
+ // (first, find the MapItem that points to StringDataItems...)
+ int idx = 0;
+ for (MapItem mapItem : rawDexFile.mapList.mapItems) {
+ if (mapItem.type == MapItem.TYPE_STRING_DATA_ITEM) {
+ break;
+ }
+ idx++;
+ }
+ // (now insert the TypeList MapItem just before the StringDataItem one...)
+ rawDexFile.mapList.mapItems.add(idx, typeListMapItem);
+ }
+
+ private void addFieldIdsToHeaderAndMapFile(RawDexFile rawDexFile,
+ Offsettable fieldOffsettable) {
+ // Add the field IDs to the header.
+ rawDexFile.header.fieldIdsOff.unsetNullAndPointTo(fieldOffsettable);
+ rawDexFile.header.fieldIdsSize = 1;
+
+ // Create a MapItem for the field IDs.
+ MapItem fieldMapItem = new MapItem();
+ fieldMapItem.offset = new Offset(false);
+ fieldMapItem.offset.pointToNew(fieldOffsettable);
+ fieldMapItem.type = MapItem.TYPE_FIELD_ID_ITEM;
+ fieldMapItem.size = 1;
+
+ // Insert into the MapList.
+ // (first, find the MapItem that points to MethodIdItems...)
+ int idx = 0;
+ for (MapItem mapItem : rawDexFile.mapList.mapItems) {
+ if (mapItem.type == MapItem.TYPE_METHOD_ID_ITEM) {
+ break;
+ }
+ idx++;
+ }
+ // (now insert the FieldIdItem MapItem just before the MethodIdItem one...)
+ rawDexFile.mapList.mapItems.add(idx, fieldMapItem);
+ }
+
+
+ private void updateOffsetsInHeaderAndMapFile(RawDexFile rawDexFile,
+ Offsettable newFirstOffsettable) {
+ Offsettable prevFirstOffsettable = null;
+ for (int i = 0; i < offsettableTable.size(); i++) {
+ if (offsettableTable.get(i) == newFirstOffsettable) {
+ prevFirstOffsettable = offsettableTable.get(i + 1);
+ break;
+ }
+ }
+ if (prevFirstOffsettable == null) {
+ Log.errorAndQuit("When calling updateMapListOffsets, could not find new "
+ + "first offsettable?");
+ }
+
+ // Based on the type of the item we just added, check the relevant Offset in the header
+ // and if it pointed at the prev_first_offsettable, make it point at the new one.
+ // NB: if it isn't pointing at the prev one, something is wrong.
+ HeaderItem header = rawDexFile.header;
+ if (newFirstOffsettable.getItem() instanceof StringIdItem) {
+ updateHeaderOffsetIfValid(header.stringIdsOff, prevFirstOffsettable,
+ newFirstOffsettable, "StringID");
+ } else if (newFirstOffsettable.getItem() instanceof TypeIdItem) {
+ updateHeaderOffsetIfValid(header.typeIdsOff, prevFirstOffsettable,
+ newFirstOffsettable, "TypeID");
+ } else if (newFirstOffsettable.getItem() instanceof ProtoIdItem) {
+ updateHeaderOffsetIfValid(header.protoIdsOff, prevFirstOffsettable,
+ newFirstOffsettable, "ProtoID");
+ } else if (newFirstOffsettable.getItem() instanceof FieldIdItem) {
+ updateHeaderOffsetIfValid(header.fieldIdsOff, prevFirstOffsettable,
+ newFirstOffsettable, "FieldID");
+ } else if (newFirstOffsettable.getItem() instanceof MethodIdItem) {
+ updateHeaderOffsetIfValid(header.methodIdsOff, prevFirstOffsettable,
+ newFirstOffsettable, "MethodID");
+ } else if (newFirstOffsettable.getItem() instanceof ClassDefItem) {
+ updateHeaderOffsetIfValid(header.classDefsOff, prevFirstOffsettable,
+ newFirstOffsettable, "ClassDef");
+ }
+
+ // Now iterate through the MapList's MapItems, and see if their Offsets pointed at the
+ // prev_first_offsettable, and if so, make them now point at the new_first_offsettable.
+ for (MapItem mapItem : rawDexFile.mapList.mapItems) {
+ if (mapItem.offset.pointsToThisOffsettable(prevFirstOffsettable)) {
+ Log.info("Updating offset in MapItem (type: " + mapItem.type + ") after "
+ + "we called insertNewOffsettableAsFirstOfType()");
+ mapItem.offset.pointToNew(newFirstOffsettable);
+ }
+ }
+ }
+
+ private void insertOffsettableAt(int idx, Offsettable offsettable) {
+ offsettableTable.add(idx, offsettable);
+ if (indexAfterMapList > idx) {
+ indexAfterMapList++;
+ }
+ if (restorePoint > idx) {
+ restorePoint++;
+ }
+ }
+
+ /**
+ * If we're creating our first TypeList, then IdCreator has to call this method to
+ * ensure it gets put into the correct place in the offsettable table.
+ * This assumes TypeLists always come before StringDatas.
+ */
+ public Offsettable insertNewOffsettableAsFirstEverTypeList(RawDexObject item,
+ RawDexFile rawDexFile) {
+ // We find the first StringDataItem, the type lists will come before this.
+ Log.info("Calling insertNewOffsettableAsFirstEverTypeList()");
+ for (int i = 0; i < offsettableTable.size(); i++) {
+ if (offsettableTable.get(i).getItem() instanceof StringDataItem) {
+ Offsettable offsettable = new Offsettable(item, true);
+ insertOffsettableAt(i, offsettable);
+ addTypeListsToMapFile(rawDexFile, offsettable);
+ return offsettable;
+ }
+ }
+ Log.errorAndQuit("Could not find any StringDataItems to insert the type list before.");
+ return null;
+ }
+
+ /**
+ * If we're creating our first FieldId, then IdCreator has to call this method to
+ * ensure it gets put into the correct place in the offsettable table.
+ * This assumes FieldIds always come before MethodIds.
+ */
+ public Offsettable insertNewOffsettableAsFirstEverField(RawDexObject item,
+ RawDexFile rawDexFile) {
+ // We find the first MethodIdItem, the fields will come before this.
+ Log.info("Calling insertNewOffsettableAsFirstEverField()");
+ for (int i = 0; i < offsettableTable.size(); i++) {
+ if (offsettableTable.get(i).getItem() instanceof MethodIdItem) {
+ Offsettable offsettable = new Offsettable(item, true);
+ insertOffsettableAt(i, offsettable);
+ addFieldIdsToHeaderAndMapFile(rawDexFile, offsettable);
+ return offsettable;
+ }
+ }
+ Log.errorAndQuit("Could not find any MethodIdItems to insert the field before.");
+ return null;
+ }
+
+ /**
+ * If we're creating a new Item (such as FieldId, MethodId) that is placed into the
+ * first position of the relevant ID table, then IdCreator has to call this method to
+ * ensure it gets put into the correct place in the offsettable table.
+ */
+ public Offsettable insertNewOffsettableAsFirstOfType(RawDexObject item,
+ RawDexFile rawDexFile) {
+ Log.debug("Calling insertNewOffsettableAsFirstOfType()");
+ int index = getOffsettableIndexForFirstItemType(item);
+ if (index == -1) {
+ Log.errorAndQuit("Could not find any object of class: " + item.getClass());
+ }
+ Offsettable offsettable = new Offsettable(item, true);
+ insertOffsettableAt(index, offsettable);
+ updateOffsetsInHeaderAndMapFile(rawDexFile, offsettable);
+ return offsettable;
+ }
+
+ /**
+ * IdCreator has to call this method when it creates a new IdItem, to make sure it
+ * gets put into the correct place in the offsettable table. IdCreator should
+ * provide the IdItem that should come before this new IdItem.
+ */
+ public Offsettable insertNewOffsettableAfter(RawDexObject item, RawDexObject itemBefore) {
+ Log.debug("Calling insertNewOffsettableAfter()");
+ int index = getOffsettableIndexForItem(itemBefore);
+ if (index == -1) {
+ Log.errorAndQuit("Did not find specified 'after' object in offsettable table.");
+ }
+ Offsettable offsettable = new Offsettable(item, true);
+ insertOffsettableAt(index + 1, offsettable);
+ return offsettable;
+ }
+
+ private int getOffsettableIndexForFirstItemType(RawDexObject item) {
+ Class<?> itemClass = item.getClass();
+ for (int i = 0; i < offsettableTable.size(); i++) {
+ if (offsettableTable.get(i).getItem().getClass().equals(itemClass)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private int getOffsettableIndexForItem(RawDexObject item) {
+ for (int i = 0; i < offsettableTable.size(); i++) {
+ if (offsettableTable.get(i).getItem() == item) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Given a RawDexObject, get the Offsettable that contains it.
+ */
+ public Offsettable getOffsettableForItem(RawDexObject item) {
+ for (int i = 0; i < offsettableTable.size(); i++) {
+ if (offsettableTable.get(i).getItem() == item) {
+ return offsettableTable.get(i);
+ }
+ }
+ return null;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/Offsettable.java b/tools/dexfuzz/src/dexfuzz/rawdex/Offsettable.java
new file mode 100644
index 0000000..1b8cb24
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/Offsettable.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+/**
+ * Tracks the original and updated positions of a RawDexObject when it is
+ * parsed in from a DEX file, and written out to a mutated DEX file.
+ */
+public class Offsettable {
+ /**
+ * The position of this Offsettable's item when it was read in.
+ */
+ private int originalPosition;
+
+ /**
+ * Set as we write out any Offsettable, so the Offset knows what its
+ * new value should be.
+ */
+ private int newPosition;
+
+ /**
+ * The actual Item this Offsettable contains.
+ */
+ private RawDexObject item;
+
+ /**
+ * Set either when getOriginalPosition() is called by the OffsetTracker
+ * to put the location in the offsettable map, so when Offsets are being
+ * associated, they know which Offsettable to point at.
+ * Or when an Offsettable is created that is marked as new, so we don't
+ * need to know its original position, because an Offset will be directly
+ * associated with it.
+ */
+ private boolean originalPositionKnown;
+
+ /**
+ * Set when we calculate the new position of this Offsettable as the file is
+ * being output.
+ */
+ private boolean updated;
+
+ /**
+ * Only the OffsetTracker should be able to create a new Offsettable.
+ */
+ public Offsettable(RawDexObject item, boolean isNew) {
+ this.item = item;
+ if (isNew) {
+ // We no longer care about the original position of the Offsettable, because
+ // we are at the stage where we manually point Offsets at Offsettables, and
+ // don't need to use the OffsetTracker's offsettable map.
+ // So just lie and say we know it now.
+ originalPositionKnown = true;
+ }
+ }
+
+ public RawDexObject getItem() {
+ return item;
+ }
+
+ /**
+ * Gets the offset from the beginning of the file to the RawDexObject this Offsettable
+ * contains, when the file was originally read.
+ * Called when we're associating Offsets with Offsettables using the OffsetTracker's
+ * offsettable map.
+ */
+ public int getOriginalPosition() {
+ if (!originalPositionKnown) {
+ throw new Error("Cannot get the original position of an Offsettable when not yet set.");
+ }
+ return originalPosition;
+ }
+
+ public void setOriginalPosition(int pos) {
+ originalPosition = pos;
+ originalPositionKnown = true;
+ }
+
+ /**
+ * Get the new position of this Offsettable, once it's been written out to the output file.
+ */
+ public int getNewPosition() {
+ if (!updated) {
+ throw new Error("Cannot request new position before it has been set!");
+ }
+ return newPosition;
+ }
+
+ /**
+ * Record the new position of this Offsettable, as it is written out to the output file.
+ */
+ public void setNewPosition(int pos) {
+ if (!updated) {
+ newPosition = pos;
+ updated = true;
+ } else {
+ throw new Error("Cannot update an Offsettable twice!");
+ }
+ }
+
+ public boolean readyForFinalOffsetToBeWritten() {
+ return (originalPositionKnown && updated);
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/Opcode.java b/tools/dexfuzz/src/dexfuzz/rawdex/Opcode.java
new file mode 100644
index 0000000..312e855
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/Opcode.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+public enum Opcode {
+ NOP,
+ MOVE,
+ MOVE_FROM16,
+ MOVE_16,
+ MOVE_WIDE,
+ MOVE_WIDE_FROM16,
+ MOVE_WIDE_16,
+ MOVE_OBJECT,
+ MOVE_OBJECT_FROM16,
+ MOVE_OBJECT_16,
+ MOVE_RESULT,
+ MOVE_RESULT_WIDE,
+ MOVE_RESULT_OBJECT,
+ MOVE_EXCEPTION,
+ RETURN_VOID,
+ RETURN,
+ RETURN_WIDE,
+ RETURN_OBJECT,
+ CONST_4,
+ CONST_16,
+ CONST,
+ CONST_HIGH16,
+ CONST_WIDE_16,
+ CONST_WIDE_32,
+ CONST_WIDE,
+ CONST_WIDE_HIGH16,
+ CONST_STRING,
+ CONST_STRING_JUMBO,
+ CONST_CLASS,
+ MONITOR_ENTER,
+ MONITOR_EXIT,
+ CHECK_CAST,
+ INSTANCE_OF,
+ ARRAY_LENGTH,
+ NEW_INSTANCE,
+ NEW_ARRAY,
+ FILLED_NEW_ARRAY,
+ FILLED_NEW_ARRAY_RANGE,
+ FILL_ARRAY_DATA,
+ THROW,
+ GOTO,
+ GOTO_16,
+ GOTO_32,
+ PACKED_SWITCH,
+ SPARSE_SWITCH,
+ CMPL_FLOAT,
+ CMPG_FLOAT,
+ CMPL_DOUBLE,
+ CMPG_DOUBLE,
+ CMP_LONG,
+ IF_EQ,
+ IF_NE,
+ IF_LT,
+ IF_GE,
+ IF_GT,
+ IF_LE,
+ IF_EQZ,
+ IF_NEZ,
+ IF_LTZ,
+ IF_GEZ,
+ IF_GTZ,
+ IF_LEZ,
+ UNUSED_3E,
+ UNUSED_3F,
+ UNUSED_40,
+ UNUSED_41,
+ UNUSED_42,
+ UNUSED_43,
+ AGET,
+ AGET_WIDE,
+ AGET_OBJECT,
+ AGET_BOOLEAN,
+ AGET_BYTE,
+ AGET_CHAR,
+ AGET_SHORT,
+ APUT,
+ APUT_WIDE,
+ APUT_OBJECT,
+ APUT_BOOLEAN,
+ APUT_BYTE,
+ APUT_CHAR,
+ APUT_SHORT,
+ IGET,
+ IGET_WIDE,
+ IGET_OBJECT,
+ IGET_BOOLEAN,
+ IGET_BYTE,
+ IGET_CHAR,
+ IGET_SHORT,
+ IPUT,
+ IPUT_WIDE,
+ IPUT_OBJECT,
+ IPUT_BOOLEAN,
+ IPUT_BYTE,
+ IPUT_CHAR,
+ IPUT_SHORT,
+ SGET,
+ SGET_WIDE,
+ SGET_OBJECT,
+ SGET_BOOLEAN,
+ SGET_BYTE,
+ SGET_CHAR,
+ SGET_SHORT,
+ SPUT,
+ SPUT_WIDE,
+ SPUT_OBJECT,
+ SPUT_BOOLEAN,
+ SPUT_BYTE,
+ SPUT_CHAR,
+ SPUT_SHORT,
+ INVOKE_VIRTUAL,
+ INVOKE_SUPER,
+ INVOKE_DIRECT,
+ INVOKE_STATIC,
+ INVOKE_INTERFACE,
+ RETURN_VOID_BARRIER,
+ INVOKE_VIRTUAL_RANGE,
+ INVOKE_SUPER_RANGE,
+ INVOKE_DIRECT_RANGE,
+ INVOKE_STATIC_RANGE,
+ INVOKE_INTERFACE_RANGE,
+ UNUSED_79,
+ UNUSED_7A,
+ NEG_INT,
+ NOT_INT,
+ NEG_LONG,
+ NOT_LONG,
+ NEG_FLOAT,
+ NEG_DOUBLE,
+ INT_TO_LONG,
+ INT_TO_FLOAT,
+ INT_TO_DOUBLE,
+ LONG_TO_INT,
+ LONG_TO_FLOAT,
+ LONG_TO_DOUBLE,
+ FLOAT_TO_INT,
+ FLOAT_TO_LONG,
+ FLOAT_TO_DOUBLE,
+ DOUBLE_TO_INT,
+ DOUBLE_TO_LONG,
+ DOUBLE_TO_FLOAT,
+ INT_TO_BYTE,
+ INT_TO_CHAR,
+ INT_TO_SHORT,
+ ADD_INT,
+ SUB_INT,
+ MUL_INT,
+ DIV_INT,
+ REM_INT,
+ AND_INT,
+ OR_INT,
+ XOR_INT,
+ SHL_INT,
+ SHR_INT,
+ USHR_INT,
+ ADD_LONG,
+ SUB_LONG,
+ MUL_LONG,
+ DIV_LONG,
+ REM_LONG,
+ AND_LONG,
+ OR_LONG,
+ XOR_LONG,
+ SHL_LONG,
+ SHR_LONG,
+ USHR_LONG,
+ ADD_FLOAT,
+ SUB_FLOAT,
+ MUL_FLOAT,
+ DIV_FLOAT,
+ REM_FLOAT,
+ ADD_DOUBLE,
+ SUB_DOUBLE,
+ MUL_DOUBLE,
+ DIV_DOUBLE,
+ REM_DOUBLE,
+ ADD_INT_2ADDR,
+ SUB_INT_2ADDR,
+ MUL_INT_2ADDR,
+ DIV_INT_2ADDR,
+ REM_INT_2ADDR,
+ AND_INT_2ADDR,
+ OR_INT_2ADDR,
+ XOR_INT_2ADDR,
+ SHL_INT_2ADDR,
+ SHR_INT_2ADDR,
+ USHR_INT_2ADDR,
+ ADD_LONG_2ADDR,
+ SUB_LONG_2ADDR,
+ MUL_LONG_2ADDR,
+ DIV_LONG_2ADDR,
+ REM_LONG_2ADDR,
+ AND_LONG_2ADDR,
+ OR_LONG_2ADDR,
+ XOR_LONG_2ADDR,
+ SHL_LONG_2ADDR,
+ SHR_LONG_2ADDR,
+ USHR_LONG_2ADDR,
+ ADD_FLOAT_2ADDR,
+ SUB_FLOAT_2ADDR,
+ MUL_FLOAT_2ADDR,
+ DIV_FLOAT_2ADDR,
+ REM_FLOAT_2ADDR,
+ ADD_DOUBLE_2ADDR,
+ SUB_DOUBLE_2ADDR,
+ MUL_DOUBLE_2ADDR,
+ DIV_DOUBLE_2ADDR,
+ REM_DOUBLE_2ADDR,
+ ADD_INT_LIT16,
+ RSUB_INT,
+ MUL_INT_LIT16,
+ DIV_INT_LIT16,
+ REM_INT_LIT16,
+ AND_INT_LIT16,
+ OR_INT_LIT16,
+ XOR_INT_LIT16,
+ ADD_INT_LIT8,
+ RSUB_INT_LIT8,
+ MUL_INT_LIT8,
+ DIV_INT_LIT8,
+ REM_INT_LIT8,
+ AND_INT_LIT8,
+ OR_INT_LIT8,
+ XOR_INT_LIT8,
+ SHL_INT_LIT8,
+ SHR_INT_LIT8,
+ USHR_INT_LIT8,
+ IGET_QUICK,
+ IGET_WIDE_QUICK,
+ IGET_OBJECT_QUICK,
+ IPUT_QUICK,
+ IPUT_WIDE_QUICK,
+ IPUT_OBJECT_QUICK,
+ INVOKE_VIRTUAL_QUICK,
+ INVOKE_VIRTUAL_QUICK_RANGE,
+ IPUT_BOOLEAN_QUICK,
+ IPUT_BYTE_QUICK,
+ IPUT_CHAR_QUICK,
+ IPUT_SHORT_QUICK,
+ UNUSED_EF,
+ UNUSED_F0,
+ UNUSED_F1,
+ UNUSED_F2,
+ UNUSED_F3,
+ UNUSED_F4,
+ UNUSED_F5,
+ UNUSED_F6,
+ UNUSED_F7,
+ UNUSED_F8,
+ UNUSED_F9,
+ UNUSED_FA,
+ UNUSED_FB,
+ UNUSED_FC,
+ UNUSED_FD,
+ UNUSED_FE,
+ UNUSED_FF;
+
+ public static boolean isBetween(Opcode opcode, Opcode opcode1, Opcode opcode2) {
+ return (opcode.ordinal() >= opcode1.ordinal() && opcode.ordinal() <= opcode2.ordinal());
+ }
+} \ No newline at end of file
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/OpcodeInfo.java b/tools/dexfuzz/src/dexfuzz/rawdex/OpcodeInfo.java
new file mode 100644
index 0000000..fb1d093
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/OpcodeInfo.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import dexfuzz.rawdex.formats.AbstractFormat;
+
+/**
+ * Every Instruction points to an OpcodeInfo object that holds useful information
+ * about that kind of instruction, including the Format that allows us to read the
+ * instructions fields correctly.
+ */
+public class OpcodeInfo {
+ public final Opcode opcode;
+ public final String name;
+ public final int value;
+ public final AbstractFormat format;
+
+ /**
+ * Construct an OpcodeInfo. A static list of these is created in Instruction.java.
+ */
+ public OpcodeInfo(Opcode opcode, String name, int opcodeValue, AbstractFormat fmt) {
+ this.opcode = opcode;
+ this.name = name;
+ this.value = opcodeValue;
+ this.format = fmt;
+ }
+} \ No newline at end of file
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/ParameterAnnotation.java b/tools/dexfuzz/src/dexfuzz/rawdex/ParameterAnnotation.java
new file mode 100644
index 0000000..0db5efd
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/ParameterAnnotation.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class ParameterAnnotation implements RawDexObject {
+ public int methodIdx;
+ public Offset annotationsOff;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ methodIdx = file.readUInt();
+ annotationsOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.writeUInt(methodIdx);
+ file.getOffsetTracker().tryToWriteOffset(annotationsOff, file, false /* ULEB128 */);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ if (kind == IndexUpdateKind.METHOD_ID && methodIdx >= insertedIdx) {
+ methodIdx++;
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/ProtoIdItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/ProtoIdItem.java
new file mode 100644
index 0000000..3ccc82f
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/ProtoIdItem.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class ProtoIdItem implements RawDexObject {
+ public int shortyIdx;
+ public int returnTypeIdx;
+ public Offset parametersOff;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().getNewOffsettable(file, this);
+ shortyIdx = file.readUInt();
+ returnTypeIdx = file.readUInt();
+ parametersOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().updatePositionOfNextOffsettable(file);
+ file.writeUInt(shortyIdx);
+ file.writeUInt(returnTypeIdx);
+ file.getOffsetTracker().tryToWriteOffset(parametersOff, file, false /* ULEB128 */);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ if (kind == IndexUpdateKind.STRING_ID && shortyIdx >= insertedIdx) {
+ shortyIdx++;
+ }
+ if (kind == IndexUpdateKind.TYPE_ID && returnTypeIdx >= insertedIdx) {
+ returnTypeIdx++;
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/RawDexFile.java b/tools/dexfuzz/src/dexfuzz/rawdex/RawDexFile.java
new file mode 100644
index 0000000..483ed6c
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/RawDexFile.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import dexfuzz.Log;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class RawDexFile implements RawDexObject {
+ private OffsetTracker offsetTracker;
+
+ public HeaderItem header;
+
+ public MapList mapList;
+
+ // Can be allocated after reading the header.
+ public List<StringIdItem> stringIds;
+ public List<TypeIdItem> typeIds;
+ public List<ProtoIdItem> protoIds;
+ public List<FieldIdItem> fieldIds;
+ public List<MethodIdItem> methodIds;
+ public List<ClassDefItem> classDefs;
+
+ // Need to be allocated later (will be allocated in MapList.java)
+ public List<StringDataItem> stringDatas;
+ public List<ClassDataItem> classDatas;
+ public List<TypeList> typeLists;
+ public List<CodeItem> codeItems;
+ public DebugInfoItem debugInfoItem;
+ public List<AnnotationsDirectoryItem> annotationsDirectoryItems;
+ public List<AnnotationSetRefList> annotationSetRefLists;
+ public List<AnnotationSetItem> annotationSetItems;
+ public List<AnnotationItem> annotationItems;
+ public List<EncodedArrayItem> encodedArrayItems;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ // Get a reference to the OffsetTracker, so that IdCreator can use it.
+ offsetTracker = file.getOffsetTracker();
+
+ file.seek(0);
+
+ // Read header.
+ (header = new HeaderItem()).read(file);
+
+ // We can allocate all of these now.
+ stringIds = new ArrayList<StringIdItem>(header.stringIdsSize);
+ typeIds = new ArrayList<TypeIdItem>(header.typeIdsSize);
+ protoIds = new ArrayList<ProtoIdItem>(header.protoIdsSize);
+ fieldIds = new ArrayList<FieldIdItem>(header.fieldIdsSize);
+ methodIds = new ArrayList<MethodIdItem>(header.methodIdsSize);
+ classDefs = new ArrayList<ClassDefItem>(header.classDefsSize);
+
+ mapList = new MapList(this);
+ mapList.read(file);
+
+ file.getOffsetTracker().associateOffsets();
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.seek(0);
+
+ // We read the header first, and then the map list, and then everything
+ // else. Therefore, when we get to the end of the header, tell OffsetTracker
+ // to skip past the map list offsets, and then when we get to the map list,
+ // tell OffsetTracker to skip back there, and then return to where it was previously.
+
+ // Update the map items' sizes first
+ // - but only update the items that we expect to have changed size.
+ // ALSO update the header's table sizes!
+ for (MapItem mapItem : mapList.mapItems) {
+ switch (mapItem.type) {
+ case MapItem.TYPE_STRING_ID_ITEM:
+ if (mapItem.size != stringIds.size()) {
+ Log.debug("Updating StringIDs List size: " + stringIds.size());
+ mapItem.size = stringIds.size();
+ header.stringIdsSize = stringIds.size();
+ }
+ break;
+ case MapItem.TYPE_STRING_DATA_ITEM:
+ if (mapItem.size != stringDatas.size()) {
+ Log.debug("Updating StringDatas List size: " + stringDatas.size());
+ mapItem.size = stringDatas.size();
+ }
+ break;
+ case MapItem.TYPE_METHOD_ID_ITEM:
+ if (mapItem.size != methodIds.size()) {
+ Log.debug("Updating MethodIDs List size: " + methodIds.size());
+ mapItem.size = methodIds.size();
+ header.methodIdsSize = methodIds.size();
+ }
+ break;
+ case MapItem.TYPE_FIELD_ID_ITEM:
+ if (mapItem.size != fieldIds.size()) {
+ Log.debug("Updating FieldIDs List size: " + fieldIds.size());
+ mapItem.size = fieldIds.size();
+ header.fieldIdsSize = fieldIds.size();
+ }
+ break;
+ case MapItem.TYPE_PROTO_ID_ITEM:
+ if (mapItem.size != protoIds.size()) {
+ Log.debug("Updating ProtoIDs List size: " + protoIds.size());
+ mapItem.size = protoIds.size();
+ header.protoIdsSize = protoIds.size();
+ }
+ break;
+ case MapItem.TYPE_TYPE_ID_ITEM:
+ if (mapItem.size != typeIds.size()) {
+ Log.debug("Updating TypeIDs List size: " + typeIds.size());
+ mapItem.size = typeIds.size();
+ header.typeIdsSize = typeIds.size();
+ }
+ break;
+ case MapItem.TYPE_TYPE_LIST:
+ if (mapItem.size != typeLists.size()) {
+ Log.debug("Updating TypeLists List size: " + typeLists.size());
+ mapItem.size = typeLists.size();
+ }
+ break;
+ default:
+ }
+ }
+
+ // Use the map list to write the file.
+ for (MapItem mapItem : mapList.mapItems) {
+ switch (mapItem.type) {
+ case MapItem.TYPE_HEADER_ITEM:
+ header.write(file);
+ file.getOffsetTracker().skipToAfterMapList();
+ break;
+ case MapItem.TYPE_STRING_ID_ITEM:
+ if (mapItem.size != stringIds.size()) {
+ Log.errorAndQuit("MapItem's size " + mapItem.size
+ + " no longer matches StringIDs table size " + stringIds.size());
+ }
+ for (StringIdItem stringId : stringIds) {
+ stringId.write(file);
+ }
+ break;
+ case MapItem.TYPE_TYPE_ID_ITEM:
+ if (mapItem.size != typeIds.size()) {
+ Log.errorAndQuit("MapItem's size " + mapItem.size
+ + " no longer matches TypeIDs table size " + typeIds.size());
+ }
+ for (TypeIdItem typeId : typeIds) {
+ typeId.write(file);
+ }
+ break;
+ case MapItem.TYPE_PROTO_ID_ITEM:
+ if (mapItem.size != protoIds.size()) {
+ Log.errorAndQuit("MapItem's size " + mapItem.size
+ + " no longer matches ProtoIDs table size " + protoIds.size());
+ }
+ for (ProtoIdItem protoId : protoIds) {
+ protoId.write(file);
+ }
+ break;
+ case MapItem.TYPE_FIELD_ID_ITEM:
+ if (mapItem.size != fieldIds.size()) {
+ Log.errorAndQuit("MapItem's size " + mapItem.size
+ + " no longer matches FieldIDs table size " + fieldIds.size());
+ }
+ for (FieldIdItem fieldId : fieldIds) {
+ fieldId.write(file);
+ }
+ break;
+ case MapItem.TYPE_METHOD_ID_ITEM:
+ if (mapItem.size != methodIds.size()) {
+ Log.errorAndQuit("MapItem's size " + mapItem.size
+ + " no longer matches MethodIDs table size " + methodIds.size());
+ }
+ for (MethodIdItem methodId : methodIds) {
+ methodId.write(file);
+ }
+ break;
+ case MapItem.TYPE_CLASS_DEF_ITEM:
+ if (mapItem.size != classDefs.size()) {
+ Log.errorAndQuit("MapItem's size " + mapItem.size
+ + " no longer matches ClassDefs table size " + classDefs.size());
+ }
+ for (ClassDefItem classDef : classDefs) {
+ classDef.write(file);
+ }
+ break;
+ case MapItem.TYPE_MAP_LIST:
+ file.getOffsetTracker().goBackToMapList();
+ mapList.write(file);
+ file.getOffsetTracker().goBackToPreviousPoint();
+ break;
+ case MapItem.TYPE_TYPE_LIST:
+ if (mapItem.size != typeLists.size()) {
+ Log.errorAndQuit("MapItem's size " + mapItem.size
+ + " no longer matches TypeLists table size " + typeLists.size());
+ }
+ for (TypeList typeList : typeLists) {
+ typeList.write(file);
+ }
+ break;
+ case MapItem.TYPE_ANNOTATION_SET_REF_LIST:
+ if (mapItem.size != annotationSetRefLists.size()) {
+ Log.errorAndQuit("MapItem's size " + mapItem.size
+ + " no longer matches AnnotationSetRefLists table size "
+ + annotationSetRefLists.size());
+ }
+ for (AnnotationSetRefList annotationSetRefList : annotationSetRefLists) {
+ annotationSetRefList.write(file);
+ }
+ break;
+ case MapItem.TYPE_ANNOTATION_SET_ITEM:
+ if (mapItem.size != annotationSetItems.size()) {
+ Log.errorAndQuit("MapItem's size " + mapItem.size
+ + " no longer matches AnnotationSetItems table size "
+ + annotationSetItems.size());
+ }
+ for (AnnotationSetItem annotationSetItem : annotationSetItems) {
+ annotationSetItem.write(file);
+ }
+ break;
+ case MapItem.TYPE_CLASS_DATA_ITEM:
+ if (mapItem.size != classDatas.size()) {
+ Log.errorAndQuit("MapItem's size " + mapItem.size
+ + " no longer matches ClassDataItems table size " + classDatas.size());
+ }
+ for (ClassDataItem classData : classDatas) {
+ classData.write(file);
+ }
+ break;
+ case MapItem.TYPE_CODE_ITEM:
+ if (mapItem.size != codeItems.size()) {
+ Log.errorAndQuit("MapItem's size " + mapItem.size
+ + " no longer matches CodeItems table size " + codeItems.size());
+ }
+ for (CodeItem codeItem : codeItems) {
+ codeItem.write(file);
+ }
+ break;
+ case MapItem.TYPE_STRING_DATA_ITEM:
+ if (mapItem.size != stringDatas.size()) {
+ Log.errorAndQuit("MapItem's size " + mapItem.size
+ + " no longer matches StringDataItems table size "
+ + stringDatas.size());
+ }
+ for (StringDataItem stringDataItem : stringDatas) {
+ stringDataItem.write(file);
+ }
+ break;
+ case MapItem.TYPE_DEBUG_INFO_ITEM:
+ debugInfoItem.write(file);
+ break;
+ case MapItem.TYPE_ANNOTATION_ITEM:
+ if (mapItem.size != annotationItems.size()) {
+ Log.errorAndQuit("MapItem's size " + mapItem.size
+ + " no longer matches AnnotationItems table size "
+ + annotationItems.size());
+ }
+ for (AnnotationItem annotationItem : annotationItems) {
+ annotationItem.write(file);
+ }
+ break;
+ case MapItem.TYPE_ENCODED_ARRAY_ITEM:
+ if (mapItem.size != encodedArrayItems.size()) {
+ Log.errorAndQuit("MapItem's size " + mapItem.size
+ + " no longer matches EncodedArrayItems table size "
+ + encodedArrayItems.size());
+ }
+ for (EncodedArrayItem encodedArrayItem : encodedArrayItems) {
+ encodedArrayItem.write(file);
+ }
+ break;
+ case MapItem.TYPE_ANNOTATIONS_DIRECTORY_ITEM:
+ if (mapItem.size != annotationsDirectoryItems.size()) {
+ Log.errorAndQuit("MapItem's size " + mapItem.size
+ + " no longer matches AnnotationDirectoryItems table size "
+ + annotationsDirectoryItems.size());
+ }
+ for (AnnotationsDirectoryItem annotationsDirectory : annotationsDirectoryItems) {
+ annotationsDirectory.write(file);
+ }
+ break;
+ default:
+ Log.errorAndQuit("Encountered unknown map item in map item list.");
+ }
+ }
+
+ file.getOffsetTracker().updateOffsets(file);
+ }
+
+ /**
+ * Given a DexRandomAccessFile, calculate the correct adler32 checksum for it.
+ */
+ private int calculateAdler32Checksum(DexRandomAccessFile file) throws IOException {
+ // Skip magic + checksum.
+ file.seek(12);
+ int a = 1;
+ int b = 0;
+ while (file.getFilePointer() < file.length()) {
+ a = (a + file.readUnsignedByte()) % 65521;
+ b = (b + a) % 65521;
+ }
+ return (b << 16) | a;
+ }
+
+ /**
+ * Given a DexRandomAccessFile, update the file size, data size, and checksum.
+ */
+ public void updateHeader(DexRandomAccessFile file) throws IOException {
+ // File size must be updated before checksum.
+ int newFileSize = (int) file.length();
+ file.seek(32);
+ file.writeUInt(newFileSize);
+
+ // Data size must be updated before checksum.
+ int newDataSize = newFileSize - header.dataOff.getNewPositionOfItem();
+ file.seek(104);
+ file.writeUInt(newDataSize);
+
+ // Now update the checksum.
+ int newChecksum = calculateAdler32Checksum(file);
+ file.seek(8);
+ file.writeUInt(newChecksum);
+
+ header.fileSize = newFileSize;
+ header.dataSize = newDataSize;
+ header.checksum = newChecksum;
+ }
+
+ /**
+ * This should only be called from NewItemCreator.
+ */
+ public OffsetTracker getOffsetTracker() {
+ return offsetTracker;
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ for (TypeIdItem typeId : typeIds) {
+ typeId.incrementIndex(kind, insertedIdx);
+ }
+ for (ProtoIdItem protoId : protoIds) {
+ protoId.incrementIndex(kind, insertedIdx);
+ }
+ for (FieldIdItem fieldId : fieldIds) {
+ fieldId.incrementIndex(kind, insertedIdx);
+ }
+ for (MethodIdItem methodId : methodIds) {
+ methodId.incrementIndex(kind, insertedIdx);
+ }
+ for (ClassDefItem classDef : classDefs) {
+ classDef.incrementIndex(kind, insertedIdx);
+ }
+ for (ClassDataItem classData : classDatas) {
+ classData.incrementIndex(kind, insertedIdx);
+ }
+ if (typeLists != null) {
+ for (TypeList typeList : typeLists) {
+ typeList.incrementIndex(kind, insertedIdx);
+ }
+ }
+ for (CodeItem codeItem : codeItems) {
+ codeItem.incrementIndex(kind, insertedIdx);
+ }
+ if (annotationsDirectoryItems != null) {
+ for (AnnotationsDirectoryItem annotationsDirectoryItem : annotationsDirectoryItems) {
+ annotationsDirectoryItem.incrementIndex(kind, insertedIdx);
+ }
+ }
+ if (annotationItems != null) {
+ for (AnnotationItem annotationItem : annotationItems) {
+ annotationItem.incrementIndex(kind, insertedIdx);
+ }
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/RawDexObject.java b/tools/dexfuzz/src/dexfuzz/rawdex/RawDexObject.java
new file mode 100644
index 0000000..0b67e9c
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/RawDexObject.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+/**
+ * Base class for any data structure that we may read or write from a DEX file.
+ */
+public interface RawDexObject {
+ /**
+ * Populate information for this DEX data from the file.
+ * @param file Input file, should already be "seeked" to the correct position.
+ * @throws IOException If there's a problem writing to the file.
+ */
+ public void read(DexRandomAccessFile file) throws IOException;
+
+ /**
+ * Write information for this DEX data to the file.
+ * @param file Output file, should already be "seeked" to the correct position.
+ * @throws IOException If there's a problem writing to the file.
+ */
+ public void write(DexRandomAccessFile file) throws IOException;
+
+ public static enum IndexUpdateKind {
+ STRING_ID,
+ TYPE_ID,
+ PROTO_ID,
+ FIELD_ID,
+ METHOD_ID
+ }
+
+ /**
+ * When we insert a new string, type, proto, field or method into the DEX file,
+ * this must be called. We may have inserted something into the middle of a table,
+ * so any indices pointing afterwards must be updated.
+ */
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx);
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/StringDataItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/StringDataItem.java
new file mode 100644
index 0000000..87c603e
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/StringDataItem.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import dexfuzz.Log;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+public class StringDataItem implements RawDexObject {
+ private int size;
+ private String data;
+ private byte[] dataAsBytes;
+ private boolean writeRawBytes;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().getNewOffsettable(file, this);
+ size = file.readUleb128();
+ if (size != 0) {
+ dataAsBytes = file.readDexUtf(size);
+ data = new String(dataAsBytes, StandardCharsets.US_ASCII);
+ if (size != data.length()) {
+ Log.warn("Don't have full support for decoding MUTF-8 yet, DEX file "
+ + "may be incorrectly mutated. Avoid using this test case for now.");
+ writeRawBytes = true;
+ }
+ } else {
+ // Read past the null byte.
+ file.readByte();
+ }
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().updatePositionOfNextOffsettable(file);
+ file.writeUleb128(size);
+ if (size > 0) {
+ if (writeRawBytes) {
+ file.writeDexUtf(dataAsBytes);
+ } else {
+ file.writeDexUtf(data.getBytes(StandardCharsets.US_ASCII));
+ }
+ } else {
+ // Write out the null byte.
+ file.writeByte(0);
+ }
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ // Do nothing.
+ }
+
+ public void setSize(int size) {
+ this.size = size;
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public void setString(String data) {
+ this.data = data;
+ }
+
+ public String getString() {
+ if (writeRawBytes) {
+ Log.warn("Reading a string that hasn't been properly decoded! Returning empty string.");
+ return "";
+ }
+ return data;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/StringIdItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/StringIdItem.java
new file mode 100644
index 0000000..da8c294
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/StringIdItem.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class StringIdItem implements RawDexObject {
+ public Offset stringDataOff;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().getNewOffsettable(file, this);
+ stringDataOff = file.getOffsetTracker().getNewOffset(file.readUInt());
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().updatePositionOfNextOffsettable(file);
+ file.getOffsetTracker().tryToWriteOffset(stringDataOff, file, false /* ULEB128 */);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ // Do nothing.
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/TryItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/TryItem.java
new file mode 100644
index 0000000..99be6de
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/TryItem.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class TryItem implements RawDexObject {
+ public int startAddr;
+ public short insnCount;
+ public short handlerOff; // Not a global offset; don't need to adjust like an Offset.
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ startAddr = file.readUInt();
+ insnCount = file.readUShort();
+ handlerOff = file.readUShort();
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.writeUInt(startAddr);
+ file.writeUShort(insnCount);
+ file.writeUShort(handlerOff);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ // Do nothing.
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/TypeIdItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/TypeIdItem.java
new file mode 100644
index 0000000..bb3eff1
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/TypeIdItem.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class TypeIdItem implements RawDexObject {
+ public int descriptorIdx;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().getNewOffsettable(file, this);
+ descriptorIdx = file.readUInt();
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.getOffsetTracker().updatePositionOfNextOffsettable(file);
+ file.writeUInt(descriptorIdx);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ if (kind == IndexUpdateKind.STRING_ID && descriptorIdx >= insertedIdx) {
+ descriptorIdx++;
+ }
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/TypeItem.java b/tools/dexfuzz/src/dexfuzz/rawdex/TypeItem.java
new file mode 100644
index 0000000..a788b47
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/TypeItem.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class TypeItem implements RawDexObject {
+ public short typeIdx;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ typeIdx = file.readUShort();
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.writeUShort(typeIdx);
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ if (kind == IndexUpdateKind.TYPE_ID && typeIdx >= insertedIdx) {
+ typeIdx++;
+ }
+ }
+} \ No newline at end of file
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/TypeList.java b/tools/dexfuzz/src/dexfuzz/rawdex/TypeList.java
new file mode 100644
index 0000000..90a2b57
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/TypeList.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex;
+
+import java.io.IOException;
+
+public class TypeList implements RawDexObject {
+ public int size;
+ public TypeItem[] list;
+
+ @Override
+ public void read(DexRandomAccessFile file) throws IOException {
+ file.alignForwards(4);
+ file.getOffsetTracker().getNewOffsettable(file, this);
+ size = file.readUInt();
+ list = new TypeItem[size];
+ for (int i = 0; i < size; i++) {
+ (list[i] = new TypeItem()).read(file);
+ }
+ }
+
+ @Override
+ public void write(DexRandomAccessFile file) throws IOException {
+ file.alignForwards(4);
+ file.getOffsetTracker().updatePositionOfNextOffsettable(file);
+ file.writeUInt(size);
+ for (TypeItem typeItem : list) {
+ typeItem.write(file);
+ }
+ }
+
+ @Override
+ public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
+ for (TypeItem type : list) {
+ type.incrementIndex(kind, insertedIdx);
+ }
+ }
+
+ /**
+ * Returns if this TypeList comes before the provided TypeList, considering the legal
+ * ordering of TypeLists in DEX files.
+ */
+ public boolean comesBefore(TypeList other) {
+ int checkSize = Math.min(size, other.size);
+ for (int i = 0; i < checkSize; i++) {
+ if (list[i].typeIdx < other.list[i].typeIdx) {
+ return true;
+ } else if (list[i].typeIdx > other.list[i].typeIdx) {
+ return false;
+ }
+ }
+ if (size == other.size) {
+ return false;
+ }
+ return true;
+ }
+} \ No newline at end of file
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/AbstractFormat.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/AbstractFormat.java
new file mode 100644
index 0000000..29b15ae
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/AbstractFormat.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+/**
+ * Every Format subclasses this AbstractFormat. The subclasses then implement these
+ * methods to write out a provided Instruction according to this format, and also methods
+ * to read the vregs from an Instruction's raw bytes.
+ * Hierarchy is as follows:
+ * AbstractFormat
+ * |____________Format1
+ * | |_____Format10t
+ * | |_____Format10x
+ * | |_____Format11n
+ * | |_____Format11x
+ * | |_____Format12x
+ * |____________Format2
+ * | |_____Format20bc
+ * | |_____Format20t
+ * etc...
+ */
+public abstract class AbstractFormat {
+ /**
+ * Get the size of an Instruction that has this format.
+ */
+ public abstract int getSize();
+
+ /**
+ * Given a file handle and an instruction, write that Instruction out to the file
+ * correctly, considering the current format.
+ */
+ public abstract void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException;
+
+ /**
+ * Read the value of vA, considering this format.
+ */
+ public abstract long getA(byte[] raw) throws IOException;
+
+ /**
+ * Read the value of vB, considering this format.
+ */
+ public abstract long getB(byte[] raw) throws IOException;
+
+ /**
+ * Read the value of vC, considering this format.
+ */
+ public abstract long getC(byte[] raw) throws IOException;
+
+ /**
+ * Only Format35c should return true for this.
+ */
+ public abstract boolean needsInvokeFormatInfo();
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsConst.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsConst.java
new file mode 100644
index 0000000..e2b164a
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsConst.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.Instruction;
+
+/**
+ * Every Format that contains a value that is a constant (that includes instructions like
+ * const/4, but also add-int/lit8) should implement this interface, to allow the constant
+ * part of a provided Instruction to be read and set correctly.
+ */
+public interface ContainsConst {
+ public long getConst(Instruction insn);
+
+ public void setConst(Instruction insn, long constant);
+
+ public long getConstRange();
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsPoolIndex.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsPoolIndex.java
new file mode 100644
index 0000000..5f66b0e
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsPoolIndex.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.Instruction;
+import dexfuzz.rawdex.OpcodeInfo;
+
+/**
+ * Every Format that contains a value that is an index into the ID tables of this DEX file
+ * should implement this interface, to allow the index part of a provided Instruction
+ * to be read and set correctly.
+ */
+public interface ContainsPoolIndex {
+ public enum PoolIndexKind {
+ Type,
+ Field,
+ String,
+ Method,
+ Invalid
+ }
+
+ public int getPoolIndex(Instruction insn);
+
+ public void setPoolIndex(Instruction insn, int poolIndex);
+
+ public PoolIndexKind getPoolIndexKind(OpcodeInfo info);
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsTarget.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsTarget.java
new file mode 100644
index 0000000..bb24e61
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsTarget.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.Instruction;
+
+/**
+ * Every Format that contains an offset to a target instruction
+ * should implement this interface, to allow the offset to be read and set correctly.
+ */
+public interface ContainsTarget {
+ public long getTarget(Instruction insn);
+
+ public void setTarget(Instruction insn, long target);
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsVRegs.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsVRegs.java
new file mode 100644
index 0000000..40ba5ac
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/ContainsVRegs.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+/**
+ * Every Format that contains virtual registers should implement this interface,
+ * to allow the number of virtual registers specified by the format to be found.
+ */
+public interface ContainsVRegs {
+ public int getVRegCount();
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format00x.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format00x.java
new file mode 100644
index 0000000..aae7469
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format00x.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format00x extends AbstractFormat {
+ @Override
+ public int getSize() {
+ return 0;
+ }
+
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public boolean needsInvokeFormatInfo() {
+ return false;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format1.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format1.java
new file mode 100644
index 0000000..e503513
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format1.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format1 extends AbstractFormat {
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public boolean needsInvokeFormatInfo() {
+ return false;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format10t.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format10t.java
new file mode 100644
index 0000000..a9e13f1
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format10t.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format10t extends Format1 implements ContainsTarget {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) insn.vregA);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getSignedByteFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public long getTarget(Instruction insn) {
+ return insn.vregA;
+ }
+
+ @Override
+ public void setTarget(Instruction insn, long target) {
+ insn.vregA = target;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format10x.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format10x.java
new file mode 100644
index 0000000..aabf725
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format10x.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format10x extends Format1 {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) 0); // padding
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format11n.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format11n.java
new file mode 100644
index 0000000..4b8c35c
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format11n.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format11n extends Format1 implements ContainsConst, ContainsVRegs {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) (insn.vregA | (insn.vregB << 4)));
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedLowNibbleFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedHighNibbleFromByte(raw, 1);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public long getConst(Instruction insn) {
+ return insn.vregB;
+ }
+
+ @Override
+ public void setConst(Instruction insn, long constant) {
+ insn.vregB = constant;
+ }
+
+ @Override
+ public long getConstRange() {
+ return (1 << 4);
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 1;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format11x.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format11x.java
new file mode 100644
index 0000000..e8963f1
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format11x.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format11x extends Format1 implements ContainsVRegs {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) insn.vregA);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 1;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format12x.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format12x.java
new file mode 100644
index 0000000..170ebe1
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format12x.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format12x extends Format1 implements ContainsVRegs {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) (insn.vregA | (insn.vregB << 4)));
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedLowNibbleFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedHighNibbleFromByte(raw, 1);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 2;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format2.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format2.java
new file mode 100644
index 0000000..5ddc124
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format2.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format2 extends AbstractFormat {
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public boolean needsInvokeFormatInfo() {
+ return false;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format20bc.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format20bc.java
new file mode 100644
index 0000000..4f21489
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format20bc.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+// NB: This format is only used for statically determined verification errors,
+// so we shouldn't encounter it in ART. (Or even before, as they are only written in during
+// verification, which comes after our fuzzing...)
+// Therefore, no need to say this implements ContainsPoolIndex, even though it is a *c format
+public class Format20bc extends Format2 {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) insn.vregA);
+ file.writeUShort((short) insn.vregB);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 1);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format20t.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format20t.java
new file mode 100644
index 0000000..1e33c15
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format20t.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format20t extends Format2 implements ContainsTarget {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) 0); // padding
+ file.writeUShort((short) insn.vregA);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getSignedShortFromTwoBytes(raw, 2);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public long getTarget(Instruction insn) {
+ return insn.vregA;
+ }
+
+ @Override
+ public void setTarget(Instruction insn, long target) {
+ insn.vregA = target;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21c.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21c.java
new file mode 100644
index 0000000..e60b54a
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21c.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+import dexfuzz.rawdex.Opcode;
+import dexfuzz.rawdex.OpcodeInfo;
+
+import java.io.IOException;
+
+public class Format21c extends Format2 implements ContainsVRegs, ContainsPoolIndex {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) insn.vregA);
+ file.writeUShort((short) insn.vregB);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 1;
+ }
+
+ @Override
+ public int getPoolIndex(Instruction insn) {
+ return (int) insn.vregB;
+ }
+
+ @Override
+ public void setPoolIndex(Instruction insn, int poolIndex) {
+ insn.vregB = poolIndex;
+ }
+
+ @Override
+ public PoolIndexKind getPoolIndexKind(OpcodeInfo info) {
+ if (info.opcode == Opcode.CONST_STRING) {
+ return PoolIndexKind.String;
+ }
+ if (info.opcode == Opcode.CONST_CLASS
+ || info.opcode == Opcode.CHECK_CAST
+ || info.opcode == Opcode.NEW_INSTANCE) {
+ return PoolIndexKind.Type;
+ }
+ // everything else is static field accesses
+ return PoolIndexKind.Field;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21h.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21h.java
new file mode 100644
index 0000000..c279f6c
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21h.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format21h extends Format2 implements ContainsConst, ContainsVRegs {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) insn.vregA);
+ file.writeUShort((short) insn.vregB);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getSignedShortFromTwoBytes(raw, 2);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public long getConst(Instruction insn) {
+ return insn.vregB;
+ }
+
+ @Override
+ public void setConst(Instruction insn, long constant) {
+ insn.vregB = constant;
+ }
+
+ @Override
+ public long getConstRange() {
+ return (1 << 16);
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 1;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21s.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21s.java
new file mode 100644
index 0000000..594d1a7
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21s.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format21s extends Format2 implements ContainsConst, ContainsVRegs {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) insn.vregA);
+ file.writeUShort((short) insn.vregB);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getSignedShortFromTwoBytes(raw, 2);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public long getConst(Instruction insn) {
+ return insn.vregB;
+ }
+
+ @Override
+ public void setConst(Instruction insn, long constant) {
+ insn.vregB = constant;
+ }
+
+ @Override
+ public long getConstRange() {
+ return (1 << 16);
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 1;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21t.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21t.java
new file mode 100644
index 0000000..dc3d659
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format21t.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format21t extends Format2 implements ContainsTarget, ContainsVRegs {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) insn.vregA);
+ file.writeUShort((short) insn.vregB);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getSignedShortFromTwoBytes(raw, 2);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public long getTarget(Instruction insn) {
+ return insn.vregB;
+ }
+
+ @Override
+ public void setTarget(Instruction insn, long target) {
+ insn.vregB = target;
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 1;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22b.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22b.java
new file mode 100644
index 0000000..bbdc7e3
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22b.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format22b extends Format2 implements ContainsVRegs, ContainsConst {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) insn.vregA);
+ file.writeByte((byte) insn.vregB);
+ file.writeByte((byte) insn.vregC);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 2);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return RawInsnHelper.getSignedByteFromByte(raw, 3);
+ }
+
+ @Override
+ public long getConst(Instruction insn) {
+ return insn.vregC;
+ }
+
+ @Override
+ public void setConst(Instruction insn, long constant) {
+ insn.vregC = constant;
+ }
+
+ @Override
+ public long getConstRange() {
+ return (1 << 8);
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 2;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22c.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22c.java
new file mode 100644
index 0000000..4dff336
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22c.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+import dexfuzz.rawdex.Opcode;
+import dexfuzz.rawdex.OpcodeInfo;
+
+import java.io.IOException;
+
+public class Format22c extends Format2 implements ContainsVRegs, ContainsPoolIndex {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) (insn.vregA | (insn.vregB << 4)));
+ file.writeUShort((short) insn.vregC);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedLowNibbleFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedHighNibbleFromByte(raw, 1);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2);
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 2;
+ }
+
+ @Override
+ public int getPoolIndex(Instruction insn) {
+ return (int) insn.vregC;
+ }
+
+ @Override
+ public void setPoolIndex(Instruction insn, int poolIndex) {
+ insn.vregC = poolIndex;
+ }
+
+ @Override
+ public PoolIndexKind getPoolIndexKind(OpcodeInfo info) {
+ if (info.opcode == Opcode.INSTANCE_OF || info.opcode == Opcode.NEW_ARRAY) {
+ return PoolIndexKind.Type;
+ }
+ return PoolIndexKind.Field;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22cs.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22cs.java
new file mode 100644
index 0000000..b66e14c
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22cs.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+import dexfuzz.rawdex.OpcodeInfo;
+
+import java.io.IOException;
+
+public class Format22cs extends Format2 implements ContainsVRegs, ContainsPoolIndex {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ throw new Error("Did not expect to have to write a 22cs instruction!");
+ // If for some reason 22cs instructions were in DEX files in the future, uncomment:
+ //file.writeByte((byte) insn.info.value);
+ //file.writeByte((byte) (insn.vreg_a | (insn.vreg_b << 4)));
+ //file.writeUShort((short) insn.vreg_c);
+ //return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedLowNibbleFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedHighNibbleFromByte(raw, 1);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2);
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 2;
+ }
+
+ @Override
+ public int getPoolIndex(Instruction insn) {
+ return (int) insn.vregC;
+ }
+
+ @Override
+ public void setPoolIndex(Instruction insn, int poolIndex) {
+ insn.vregC = poolIndex;
+ }
+
+ @Override
+ public PoolIndexKind getPoolIndexKind(OpcodeInfo info) {
+ // This should technically be a byte offset, but we should never encounter
+ // this format during DEX mutation anyway. (see writeToFile())
+ return PoolIndexKind.Field;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22s.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22s.java
new file mode 100644
index 0000000..9497179
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22s.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format22s extends Format2 implements ContainsVRegs, ContainsConst {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) (insn.vregA | (insn.vregB << 4)));
+ file.writeUShort((short) insn.vregC);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedLowNibbleFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedHighNibbleFromByte(raw, 1);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return RawInsnHelper.getSignedShortFromTwoBytes(raw, 2);
+ }
+
+ @Override
+ public long getConst(Instruction insn) {
+ return insn.vregC;
+ }
+
+ @Override
+ public void setConst(Instruction insn, long constant) {
+ insn.vregC = constant;
+ }
+
+ @Override
+ public long getConstRange() {
+ return (1 << 16);
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 2;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22t.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22t.java
new file mode 100644
index 0000000..5ea9579
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22t.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format22t extends Format2 implements ContainsTarget, ContainsVRegs {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) (insn.vregA | (insn.vregB << 4)));
+ file.writeUShort((short) insn.vregC);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedLowNibbleFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedHighNibbleFromByte(raw, 1);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return RawInsnHelper.getSignedShortFromTwoBytes(raw, 2);
+ }
+
+ @Override
+ public long getTarget(Instruction insn) {
+ return insn.vregC;
+ }
+
+ @Override
+ public void setTarget(Instruction insn, long target) {
+ insn.vregC = target;
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 2;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22x.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22x.java
new file mode 100644
index 0000000..5cd3d73
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format22x.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format22x extends Format2 implements ContainsVRegs {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) insn.vregA);
+ file.writeUShort((short) insn.vregB);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 2;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format23x.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format23x.java
new file mode 100644
index 0000000..8ce7c7c
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format23x.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format23x extends Format2 implements ContainsVRegs {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) insn.vregA);
+ file.writeByte((byte) insn.vregB);
+ file.writeByte((byte) insn.vregC);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 2);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 3);
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 3;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3.java
new file mode 100644
index 0000000..d9d7ce1
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format3 extends AbstractFormat {
+ @Override
+ public int getSize() {
+ return 3;
+ }
+
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public boolean needsInvokeFormatInfo() {
+ return false;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format30t.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format30t.java
new file mode 100644
index 0000000..0b62646
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format30t.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format30t extends Format3 implements ContainsTarget {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) 0); // padding
+ file.writeUInt((int) insn.vregA);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getSignedIntFromFourBytes(raw, 2);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public long getTarget(Instruction insn) {
+ return insn.vregA;
+ }
+
+ @Override
+ public void setTarget(Instruction insn, long target) {
+ insn.vregA = target;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31c.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31c.java
new file mode 100644
index 0000000..435fa19
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31c.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+import dexfuzz.rawdex.OpcodeInfo;
+
+import java.io.IOException;
+
+public class Format31c extends Format3 implements ContainsVRegs, ContainsPoolIndex {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) insn.vregA);
+ file.writeUInt((int) insn.vregB);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedIntFromFourBytes(raw, 2);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 1;
+ }
+
+ @Override
+ public int getPoolIndex(Instruction insn) {
+ return (int) insn.vregB;
+ }
+
+ @Override
+ public void setPoolIndex(Instruction insn, int poolIndex) {
+ insn.vregB = poolIndex;
+ }
+
+ @Override
+ public PoolIndexKind getPoolIndexKind(OpcodeInfo info) {
+ return PoolIndexKind.String;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31i.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31i.java
new file mode 100644
index 0000000..d54c074
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31i.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format31i extends Format3 implements ContainsConst, ContainsVRegs {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) insn.vregA);
+ file.writeUInt((int) insn.vregB);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getSignedIntFromFourBytes(raw, 2);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public long getConst(Instruction insn) {
+ return insn.vregB;
+ }
+
+ @Override
+ public void setConst(Instruction insn, long constant) {
+ insn.vregB = constant;
+ }
+
+ @Override
+ public long getConstRange() {
+ return (1L << 32);
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 1;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31t.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31t.java
new file mode 100644
index 0000000..b74db8f
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format31t.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format31t extends Format3 implements ContainsTarget, ContainsVRegs {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) insn.vregA);
+ file.writeUInt((int) insn.vregB);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getSignedIntFromFourBytes(raw, 2);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public long getTarget(Instruction insn) {
+ return insn.vregB;
+ }
+
+ @Override
+ public void setTarget(Instruction insn, long target) {
+ insn.vregB = target;
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 1;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format32x.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format32x.java
new file mode 100644
index 0000000..2f46105
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format32x.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format32x extends Format3 implements ContainsVRegs {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) 0); // padding
+ file.writeUShort((short) insn.vregA);
+ file.writeUShort((short) insn.vregB);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 4);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 2;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35c.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35c.java
new file mode 100644
index 0000000..e4a50ff
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35c.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+import dexfuzz.rawdex.Opcode;
+import dexfuzz.rawdex.OpcodeInfo;
+
+import java.io.IOException;
+
+public class Format35c extends Format3 implements ContainsPoolIndex {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) (insn.invokeFormatInfo.vregG | (insn.vregA << 4)));
+ file.writeUShort((short) insn.vregB);
+ file.writeByte((byte) ((insn.invokeFormatInfo.vregD << 4) | insn.vregC));
+ file.writeByte((byte) ((insn.invokeFormatInfo.vregF << 4)
+ | insn.invokeFormatInfo.vregE));
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedHighNibbleFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedLowNibbleFromByte(raw, 4);
+ }
+
+ @Override
+ public boolean needsInvokeFormatInfo() {
+ return true;
+ }
+
+ @Override
+ public int getPoolIndex(Instruction insn) {
+ return (int) insn.vregB;
+ }
+
+ @Override
+ public void setPoolIndex(Instruction insn, int poolIndex) {
+ insn.vregB = poolIndex;
+ }
+
+ @Override
+ public PoolIndexKind getPoolIndexKind(OpcodeInfo info) {
+ if (info.opcode == Opcode.FILLED_NEW_ARRAY) {
+ return PoolIndexKind.Type;
+ }
+ return PoolIndexKind.Method;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35mi.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35mi.java
new file mode 100644
index 0000000..f622385
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35mi.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format35mi extends Format3 {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) (insn.invokeFormatInfo.vregG | (insn.vregA << 4)));
+ file.writeUShort((short) insn.vregB);
+ file.writeByte((byte) ((insn.invokeFormatInfo.vregD << 4) | insn.vregC));
+ file.writeByte((byte) ((insn.invokeFormatInfo.vregF << 4)
+ | insn.invokeFormatInfo.vregE));
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedHighNibbleFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedLowNibbleFromByte(raw, 4);
+ }
+
+ @Override
+ public boolean needsInvokeFormatInfo() {
+ return true;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35ms.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35ms.java
new file mode 100644
index 0000000..3be9707
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format35ms.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format35ms extends Format3 {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) (insn.invokeFormatInfo.vregG | (insn.vregA << 4)));
+ file.writeUShort((short) insn.vregB);
+ file.writeByte((byte) ((insn.invokeFormatInfo.vregD << 4) | insn.vregC));
+ file.writeByte((byte) ((insn.invokeFormatInfo.vregF << 4)
+ | insn.invokeFormatInfo.vregE));
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedHighNibbleFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedLowNibbleFromByte(raw, 4);
+ }
+
+ @Override
+ public boolean needsInvokeFormatInfo() {
+ return true;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rc.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rc.java
new file mode 100644
index 0000000..630825d
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rc.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+import dexfuzz.rawdex.Opcode;
+import dexfuzz.rawdex.OpcodeInfo;
+
+import java.io.IOException;
+
+public class Format3rc extends Format3 implements ContainsPoolIndex {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) insn.vregA);
+ file.writeUShort((short) insn.vregB);
+ file.writeUShort((short) insn.vregC);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 4);
+ }
+
+ @Override
+ public int getPoolIndex(Instruction insn) {
+ return (int) insn.vregB;
+ }
+
+ @Override
+ public void setPoolIndex(Instruction insn, int poolIndex) {
+ insn.vregB = poolIndex;
+ }
+
+ @Override
+ public PoolIndexKind getPoolIndexKind(OpcodeInfo info) {
+ if (info.opcode == Opcode.FILLED_NEW_ARRAY_RANGE) {
+ return PoolIndexKind.Type;
+ }
+ return PoolIndexKind.Method;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rmi.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rmi.java
new file mode 100644
index 0000000..7b6ceea
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rmi.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format3rmi extends Format3 {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) insn.vregA);
+ file.writeUShort((short) insn.vregB);
+ file.writeUShort((short) insn.vregC);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 4);
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rms.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rms.java
new file mode 100644
index 0000000..17535f9
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format3rms.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format3rms extends Format3 {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) insn.vregA);
+ file.writeUShort((short) insn.vregB);
+ file.writeUShort((short) insn.vregC);
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 2);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedShortFromTwoBytes(raw, 4);
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format5.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format5.java
new file mode 100644
index 0000000..bc141be
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format5.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format5 extends AbstractFormat {
+ @Override
+ public int getSize() {
+ return 5;
+ }
+
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public boolean needsInvokeFormatInfo() {
+ return false;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format51l.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format51l.java
new file mode 100644
index 0000000..fc2e0ed
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/Format51l.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+import dexfuzz.rawdex.DexRandomAccessFile;
+import dexfuzz.rawdex.Instruction;
+
+import java.io.IOException;
+
+public class Format51l extends Format5 implements ContainsConst, ContainsVRegs {
+ @Override
+ public void writeToFile(DexRandomAccessFile file, Instruction insn) throws IOException {
+ file.writeByte((byte) insn.info.value);
+ file.writeByte((byte) insn.vregA);
+ file.writeUShort((short) (insn.vregB & 0xffff));
+ file.writeUShort((short) ((insn.vregB & 0xffff0000) >> 16));
+ file.writeUShort((short) ((insn.vregB & 0xffff00000000L) >> 32));
+ file.writeUShort((short) ((insn.vregB & 0xffff000000000000L) >> 48));
+ return;
+ }
+
+ @Override
+ public long getA(byte[] raw) throws IOException {
+ return RawInsnHelper.getUnsignedByteFromByte(raw, 1);
+ }
+
+ @Override
+ public long getB(byte[] raw) throws IOException {
+ return RawInsnHelper.getSignedLongFromEightBytes(raw, 2);
+ }
+
+ @Override
+ public long getC(byte[] raw) throws IOException {
+ return (long) 0;
+ }
+
+ @Override
+ public long getConst(Instruction insn) {
+ return insn.vregB;
+ }
+
+ @Override
+ public void setConst(Instruction insn, long constant) {
+ insn.vregB = constant;
+ }
+
+ @Override
+ public long getConstRange() {
+ return (1L << 63);
+ }
+
+ @Override
+ public int getVRegCount() {
+ return 1;
+ }
+}
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/formats/RawInsnHelper.java b/tools/dexfuzz/src/dexfuzz/rawdex/formats/RawInsnHelper.java
new file mode 100644
index 0000000..b16a1b5
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/rawdex/formats/RawInsnHelper.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dexfuzz.rawdex.formats;
+
+/**
+ * Class consisting of static methods used for common read/write operations
+ * perfomed in the Format classes.
+ */
+public class RawInsnHelper {
+ /**
+ * Read a signed byte from the idx into the raw array.
+ */
+ public static long getSignedByteFromByte(byte[] raw, int idx) {
+ return (long) raw[idx];
+ }
+
+ /**
+ * Read an unsigned byte from the idx into the raw array.
+ */
+ public static long getUnsignedByteFromByte(byte[] raw, int idx) {
+ return ((long) raw[idx]) & 0xff;
+ }
+
+ /**
+ * Read an unsigned lower 4 bits from the idx into the raw array.
+ */
+ public static long getUnsignedLowNibbleFromByte(byte[] raw, int idx) {
+ return ((long) raw[idx]) & 0xf;
+ }
+
+ /**
+ * Read an unsigned higher 4 bits from the idx into the raw array.
+ */
+ public static long getUnsignedHighNibbleFromByte(byte[] raw, int idx) {
+ return (((long) raw[idx]) >> 4) & 0xf;
+ }
+
+ /**
+ * Read an unsigned 2 bytes as a short from the idx into the raw array.
+ */
+ public static long getUnsignedShortFromTwoBytes(byte[] raw, int idx) {
+ return (long) ( (((long) raw[idx]) & 0xff)
+ | ((((long) raw[idx + 1]) & 0xff) << 8));
+ }
+
+ /**
+ * Read a signed 2 bytes as a short from the idx into the raw array.
+ */
+ public static long getSignedShortFromTwoBytes(byte[] raw, int idx) {
+ return (long) ( (((long) raw[idx]) & 0xff)
+ | (((long) raw[idx + 1]) << 8));
+ }
+
+ /**
+ * Read an unsigned 4 bytes as an int from the idx into the raw array.
+ */
+ public static long getUnsignedIntFromFourBytes(byte[] raw, int idx) {
+ return (long) ( (((long) raw[idx]) & 0xff)
+ | ((((long) raw[idx + 1]) & 0xff) << 8)
+ | ((((long) raw[idx + 2]) & 0xff) << 16)
+ | ((((long) raw[idx + 3]) & 0xff) << 24) );
+ }
+
+ /**
+ * Read a signed 4 bytes as an int from the idx into the raw array.
+ */
+ public static long getSignedIntFromFourBytes(byte[] raw, int idx) {
+ return (long) ( (((long) raw[idx]) & 0xff)
+ | ((((long) raw[idx + 1]) & 0xff) << 8)
+ | ((((long) raw[idx + 2]) & 0xff) << 16)
+ | (((long) raw[idx + 3]) << 24) );
+ }
+
+ /**
+ * Read a signed 8 bytes as a long from the idx into the raw array.
+ */
+ public static long getSignedLongFromEightBytes(byte[] raw, int idx) {
+ return (long) ( (((long) raw[idx]) & 0xff)
+ | ((((long) raw[idx + 1]) & 0xff) << 8)
+ | ((((long) raw[idx + 2]) & 0xff) << 16)
+ | ((((long) raw[idx + 3]) & 0xff) << 24)
+ | ((((long) raw[idx + 4]) & 0xff) << 32)
+ | ((((long) raw[idx + 5]) & 0xff) << 40)
+ | ((((long) raw[idx + 6]) & 0xff) << 48)
+ | (((long) raw[idx + 7]) << 56) );
+ }
+
+ /**
+ * Given an idx into a raw array, and an int, write that int into the array at that position.
+ */
+ public static void writeUnsignedIntToFourBytes(byte[] raw, int idx, int value) {
+ raw[idx] = (byte) (value & 0xFF);
+ raw[idx + 1] = (byte) ((value & 0xFF00) >>> 8);
+ raw[idx + 2] = (byte) ((value & 0xFF0000) >>> 16);
+ raw[idx + 3] = (byte) ((value & 0xFF000000) >>> 24);
+ }
+
+ /**
+ * Given an idx into a raw array, and a short, write that int into the array at that position.
+ */
+ public static void writeUnsignedShortToTwoBytes(byte[] raw, int idx, int value) {
+ raw[idx] = (byte) (value & 0xFF);
+ raw[idx + 1] = (byte) ((value & 0xFF00) >>> 8);
+ }
+}