aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Gruver <bgruv@google.com>2013-03-02 14:04:51 -0800
committerBen Gruver <bgruv@google.com>2013-03-02 14:04:51 -0800
commit6d607ebe1d7bccd4fdf220f0275207cb452501bd (patch)
tree6d94cd2967e07d08a8e8bcc64218e349bfc2b1b7
parentcd12f13ffc2e67e674d82060076a450051b0371b (diff)
downloadsmali-6d607ebe1d7bccd4fdf220f0275207cb452501bd.tar.gz
Annotate CodeItems
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/ClassDataItem.java57
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/CodeItem.java380
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/RawDexFile.java1
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/util/ReferenceUtil.java22
-rw-r--r--util/src/main/java/org/jf/util/NumberUtils.java141
-rw-r--r--util/src/test/java/org/jf/util/NumberUtilsTest.java128
6 files changed, 727 insertions, 2 deletions
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/ClassDataItem.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/ClassDataItem.java
index 1c98cf3d..632e56fd 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/ClassDataItem.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/ClassDataItem.java
@@ -32,12 +32,14 @@
package org.jf.dexlib2.dexbacked.raw;
import com.google.common.base.Joiner;
+import com.google.common.collect.Maps;
import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.dexbacked.DexReader;
import org.jf.dexlib2.util.AnnotatedBytes;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import java.util.HashMap;
import java.util.Map;
public class ClassDataItem {
@@ -159,4 +161,59 @@ public class ClassDataItem {
}
};
}
+
+ @Nullable public static Map<Integer, String> getCodeItemMethodMap(@Nonnull RawDexFile dexFile) {
+ MapItem classDataMap = dexFile.getMapItemForSection(ItemType.CLASS_DATA_ITEM);
+ if (classDataMap != null) {
+ HashMap<Integer, String> codeItemMethodMap = Maps.newHashMap();
+
+ int classDataOffset = classDataMap.getOffset();
+
+ DexReader reader = dexFile.readerAt(classDataOffset);
+
+ for (int i=0; i<classDataMap.getItemCount(); i++) {
+ int staticFieldsSize = reader.readSmallUleb128();
+ int instanceFieldsSize = reader.readSmallUleb128();
+ int directMethodsSize = reader.readSmallUleb128();
+ int virtualMethodsSize = reader.readSmallUleb128();
+
+ for (int j=0; j<staticFieldsSize; j++) {
+ reader.readSmallUleb128();
+ reader.readSmallUleb128();
+ }
+
+ for (int j=0; j<instanceFieldsSize; j++) {
+ reader.readSmallUleb128();
+ reader.readSmallUleb128();
+ }
+
+ int previousIndex = 0;
+ for (int j=0; j<directMethodsSize; j++) {
+ previousIndex = handleMethod(reader, previousIndex, codeItemMethodMap);
+ }
+
+ previousIndex = 0;
+ for (int j=0; j<virtualMethodsSize; j++) {
+ previousIndex = handleMethod(reader, previousIndex, codeItemMethodMap);
+ }
+ }
+
+ return codeItemMethodMap;
+ }
+ return null;
+ }
+
+ private static int handleMethod(@Nonnull DexReader reader, int previousIndex, Map<Integer, String> map) {
+ int methodIndexDelta = reader.readSmallUleb128();
+ int methodIndex = previousIndex + methodIndexDelta;
+ String methodString = MethodIdItem.asString(reader.dexBuf, methodIndex);
+
+ reader.readSmallUleb128();
+
+ int codeOffset = reader.readSmallUleb128();
+ if (codeOffset != 0) {
+ map.put(codeOffset, methodString);
+ }
+ return methodIndex;
+ }
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/CodeItem.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/CodeItem.java
new file mode 100644
index 00000000..b33a8a36
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/CodeItem.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright 2013, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.dexlib2.dexbacked.raw;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import org.jf.dexlib2.dexbacked.DexReader;
+import org.jf.dexlib2.dexbacked.instruction.DexBackedInstruction;
+import org.jf.dexlib2.iface.instruction.*;
+import org.jf.dexlib2.iface.instruction.formats.*;
+import org.jf.dexlib2.util.AnnotatedBytes;
+import org.jf.dexlib2.util.ReferenceUtil;
+import org.jf.util.NumberUtils;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.Map;
+
+public class CodeItem {
+ public static final int REGISTERS_OFFSET = 0;
+ public static final int INS_OFFSET = 2;
+ public static final int OUTS_OFFSET = 4;
+ public static final int TRIES_OFFSET = 6;
+ public static final int DEBUG_INFO_OFFSET = 8;
+ public static final int INSTRUCTION_COUNT_OFFSET = 12;
+
+ private abstract static class CodeItemAnnotator extends SectionAnnotator {
+ public CodeItemAnnotator() {
+ }
+
+ @Nonnull @Override public String getItemName() {
+ return "code_item";
+ }
+
+ @Override public int getItemAlignment() {
+ return 4;
+ }
+
+ @Override protected void annotateItem(@Nonnull AnnotatedBytes out, @Nonnull RawDexFile dexFile, int itemIndex) {
+ DexReader reader = dexFile.readerAt(out.getCursor());
+
+ int registers = reader.readUshort();
+ out.annotate(2, "registers_size = %d", registers);
+
+ int inSize = reader.readUshort();
+ out.annotate(2, "ins_size = %d", inSize);
+
+ int outSize = reader.readUshort();
+ out.annotate(2, "outs_size = %d", outSize);
+
+ int triesCount = reader.readUshort();
+ out.annotate(2, "tries_size = %d", triesCount);
+
+ int debugInfoOffset = reader.readSmallUint();
+ out.annotate(4, "debug_info_off = 0x%x", debugInfoOffset);
+
+ int instructionSize = reader.readSmallUint();
+ out.annotate(4, "insns_size = 0x%x", instructionSize);
+
+ out.annotate(0, "instructions:");
+ out.indent();
+
+ int end = reader.getOffset() + instructionSize*2;
+ while (reader.getOffset() < end) {
+ Instruction instruction = DexBackedInstruction.readFrom(reader);
+
+ switch (instruction.getOpcode().format) {
+ case Format10x:
+ annotateInstruction10x(out, instruction);
+ break;
+ case Format35c:
+ annotateInstruction35c(out, (Instruction35c)instruction);
+ break;
+ case Format3rc:
+ annotateInstruction3rc(out, (Instruction3rc)instruction);
+ break;
+ case ArrayPayload:
+ annotateArrayPayload(out, (ArrayPayload)instruction);
+ break;
+ case PackedSwitchPayload:
+ annotatePackedSwitchPayload(out, (PackedSwitchPayload)instruction);
+ break;
+ case SparseSwitchPayload:
+ annotateSparseSwitchPayload(out, (SparseSwitchPayload)instruction);
+ break;
+ default:
+ annotateDefaultInstruction(out, instruction);
+ break;
+ }
+
+ assert reader.getOffset() == out.getCursor();
+ }
+ out.deindent();
+
+ if (triesCount > 0) {
+ if ((reader.getOffset() % 4) != 0) {
+ reader.readUshort();
+ out.annotate(2, "padding");
+ }
+
+ out.annotate(0, "try_items:");
+ out.indent();
+ for (int i=0; i<triesCount; i++) {
+ out.annotate(0, "try_item[%d]:", i);
+ out.indent();
+ int startAddr = reader.readSmallUint();
+ out.annotate(4, "start_addr = 0x%x", startAddr);
+
+ int instructionCount = reader.readUshort();
+ out.annotate(2, "insn_count = 0x%x", instructionCount);
+
+ int handlerOffset = reader.readUshort();
+ out.annotate(2, "handler_off = 0x%x", handlerOffset);
+ out.deindent();
+ }
+ out.deindent();
+
+ int mark = reader.getOffset();
+ int handlerListCount = reader.readSmallUleb128();
+ out.annotate(0, "encoded_catch_handler_list:");
+ out.annotate(reader.getOffset() - mark, "size = %d", handlerListCount);
+ out.indent();
+ for (int i=0; i<handlerListCount; i++) {
+ out.annotate(0, "encoded_catch_handler[%d]", i);
+ out.indent();
+ mark = reader.getOffset();
+ int handlerCount = reader.readSleb128();
+ out.annotate(reader.getOffset() - mark, "size = %d", handlerCount);
+ boolean hasCatchAll = handlerCount <= 0;
+ handlerCount = Math.abs(handlerCount);
+ if (handlerCount != 0) {
+ out.annotate(0, "handlers:");
+ out.indent();
+ for (int j=0; j<handlerCount; j++) {
+ out.annotate(0, "encoded_type_addr_pair[%d]", i);
+ out.indent();
+ mark = reader.getOffset();
+ int typeIndex = reader.readSmallUleb128();
+ out.annotate(reader.getOffset() - mark,
+ TypeIdItem.getReferenceAnnotation(dexFile, typeIndex));
+
+ mark = reader.getOffset();
+ int handlerAddress = reader.readSmallUleb128();
+ out.annotate(reader.getOffset() - mark, "addr = 0x%x", handlerAddress);
+ out.deindent();
+ }
+ out.deindent();
+ }
+ if (hasCatchAll) {
+ mark = reader.getOffset();
+ int catchAllAddress = reader.readSmallUleb128();
+ out.annotate(reader.getOffset() - mark, "catch_all_addr = 0x%x", catchAllAddress);
+ }
+ out.deindent();
+ }
+ out.deindent();
+ }
+
+
+ }
+
+ private static void annotateInstruction10x(@Nonnull AnnotatedBytes out, @Nonnull Instruction instruction) {
+ out.annotate(2, instruction.getOpcode().name);
+ }
+
+ private static void annotateInstruction35c(@Nonnull AnnotatedBytes out, @Nonnull Instruction35c instruction) {
+ List<String> args = Lists.newArrayList();
+
+ int registerCount = instruction.getRegisterCount();
+ if (registerCount == 1) {
+ args.add(formatRegister(instruction.getRegisterC()));
+ } else if (registerCount == 2) {
+ args.add(formatRegister(instruction.getRegisterC()));
+ args.add(formatRegister(instruction.getRegisterD()));
+ } else if (registerCount == 3) {
+ args.add(formatRegister(instruction.getRegisterC()));
+ args.add(formatRegister(instruction.getRegisterD()));
+ args.add(formatRegister(instruction.getRegisterE()));
+ } else if (registerCount == 4) {
+ args.add(formatRegister(instruction.getRegisterC()));
+ args.add(formatRegister(instruction.getRegisterD()));
+ args.add(formatRegister(instruction.getRegisterE()));
+ args.add(formatRegister(instruction.getRegisterF()));
+ } else if (registerCount == 5) {
+ args.add(formatRegister(instruction.getRegisterC()));
+ args.add(formatRegister(instruction.getRegisterD()));
+ args.add(formatRegister(instruction.getRegisterE()));
+ args.add(formatRegister(instruction.getRegisterF()));
+ args.add(formatRegister(instruction.getRegisterG()));
+ }
+
+ String reference = ReferenceUtil.getReferenceString(instruction.getReference());
+
+ out.annotate(6, String.format("%s {%s}, %s",
+ instruction.getOpcode().name, Joiner.on(", ").join(args), reference));
+ }
+
+ private static void annotateInstruction3rc(@Nonnull AnnotatedBytes out, @Nonnull Instruction3rc instruction) {
+ int startRegister = instruction.getStartRegister();
+ int endRegister = startRegister + instruction.getRegisterCount() - 1;
+ String reference = ReferenceUtil.getReferenceString(instruction.getReference());
+ out.annotate(6, String.format("%s {%s .. %s}, %s",
+ instruction.getOpcode().name, formatRegister(startRegister), formatRegister(endRegister),
+ reference));
+ }
+
+ private static void annotateDefaultInstruction(@Nonnull AnnotatedBytes out, @Nonnull Instruction instruction) {
+ List<String> args = Lists.newArrayList();
+
+ if (instruction instanceof OneRegisterInstruction) {
+ args.add(formatRegister(((OneRegisterInstruction)instruction).getRegisterA()));
+ if (instruction instanceof TwoRegisterInstruction) {
+ args.add(formatRegister(((TwoRegisterInstruction)instruction).getRegisterB()));
+ if (instruction instanceof ThreeRegisterInstruction) {
+ args.add(formatRegister(((ThreeRegisterInstruction)instruction).getRegisterC()));
+ }
+ }
+ }
+
+ if (instruction instanceof ReferenceInstruction) {
+ args.add(ReferenceUtil.getReferenceString(
+ ((ReferenceInstruction)instruction).getReference()));
+ }
+
+ if (instruction instanceof OffsetInstruction) {
+ int offset = ((OffsetInstruction)instruction).getCodeOffset();
+ String sign = offset>=0?"+":"-";
+ args.add(String.format("%s0x%x", sign, offset));
+ }
+
+ if (instruction instanceof NarrowLiteralInstruction) {
+ int value = ((NarrowLiteralInstruction)instruction).getNarrowLiteral();
+ if (NumberUtils.isLikelyFloat(value)) {
+ args.add(String.format("%d # %f", value, Float.intBitsToFloat(value)));
+ } else {
+ args.add(String.format("%d", value));
+ }
+ } else if (instruction instanceof WideLiteralInstruction) {
+ long value = ((WideLiteralInstruction)instruction).getWideLiteral();
+ if (NumberUtils.isLikelyDouble(value)) {
+ args.add(String.format("%d # %f", value, Double.longBitsToDouble(value)));
+ } else {
+ args.add(String.format("%d", value));
+ }
+ }
+
+ out.annotate(instruction.getCodeUnits()*2, "%s %s",
+ instruction.getOpcode().name, Joiner.on(", ").join(args));
+ }
+
+ private void annotateArrayPayload(@Nonnull AnnotatedBytes out, @Nonnull ArrayPayload instruction) {
+ List<Number> elements = instruction.getArrayElements();
+ int elementWidth = instruction.getElementWidth();
+
+ out.annotate(2, instruction.getOpcode().name);
+ out.indent();
+ out.annotate(2, "element_width = %d", elementWidth);
+ out.annotate(4, "size = %d", elements.size());
+ out.annotate(0, "elements:");
+ out.indent();
+ for (int i=0; i<elements.size(); i++) {
+ if (elementWidth == 8) {
+ long value = elements.get(i).longValue();
+ if (NumberUtils.isLikelyDouble(value)) {
+ out.annotate(elementWidth, "element[%d] = %d # %f", i, value, Double.longBitsToDouble(value));
+ } else {
+ out.annotate(elementWidth, "element[%d] = %d", i, value);
+ }
+ } else {
+ int value = elements.get(i).intValue();
+ if (NumberUtils.isLikelyFloat(value)) {
+ out.annotate(elementWidth, "element[%d] = %d # %f", i, value, Float.intBitsToFloat(value));
+ } else {
+ out.annotate(elementWidth, "element[%d] = %d", i, value);
+ }
+ }
+ }
+ if (out.getCursor() % 2 != 0) {
+ out.annotate(1, "padding");
+ }
+ out.deindent();
+ out.deindent();
+ }
+
+ private void annotatePackedSwitchPayload(@Nonnull AnnotatedBytes out,
+ @Nonnull PackedSwitchPayload instruction) {
+ List<? extends SwitchElement> elements = instruction.getSwitchElements();
+
+ out.annotate(2, instruction.getOpcode().name);
+ out.indent();
+
+ out.annotate(2, "size = %d", elements.size());
+ out.annotate(4, "first_key = %d", elements.get(0).getKey());
+ out.annotate(0, "targets:");
+ out.indent();
+ for (int i=0; i<elements.size(); i++) {
+ out.annotate(4, "target[%d] = %d", i, elements.get(i).getOffset());
+ }
+ out.deindent();
+ out.deindent();
+ }
+
+ private void annotateSparseSwitchPayload(@Nonnull AnnotatedBytes out,
+ @Nonnull SparseSwitchPayload instruction) {
+ List<? extends SwitchElement> elements = instruction.getSwitchElements();
+
+ out.annotate(2, instruction.getOpcode().name);
+ out.indent();
+ out.annotate(2, "size = %d", elements.size());
+ out.annotate(0, "keys:");
+ out.indent();
+ for (int i=0; i<elements.size(); i++) {
+ out.annotate(4, "key[%d] = %d", i, elements.get(i).getKey());
+ }
+ out.deindent();
+ out.annotate(0, "targets:");
+ out.indent();
+ for (int i=0; i<elements.size(); i++) {
+ out.annotate(4, "target[%d] = %d", i, elements.get(i).getOffset());
+ }
+ out.deindent();
+ out.deindent();
+ }
+ }
+
+ private static String formatRegister(int registerNum) {
+ return String.format("v%d", registerNum);
+ }
+
+ @Nonnull
+ public static SectionAnnotator getAnnotator() {
+ return new CodeItemAnnotator() {
+ @Override
+ public void annotateSection(@Nonnull AnnotatedBytes out, @Nonnull RawDexFile dexFile, int itemCount) {
+ final Map<Integer, String> methodMap = ClassDataItem.getCodeItemMethodMap(dexFile);
+
+ SectionAnnotator annotator = new CodeItemAnnotator() {
+ @Nullable @Override
+ public String getItemIdentity(@Nonnull RawDexFile dexFile, int itemIndex, int itemOffset) {
+ if (methodMap != null) {
+ return methodMap.get(itemOffset);
+ }
+ return null;
+ }
+ };
+ annotator.annotateSection(out, dexFile, itemCount);
+ }
+ };
+ }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/RawDexFile.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/RawDexFile.java
index 7e249a05..d9d95b8f 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/RawDexFile.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/RawDexFile.java
@@ -97,6 +97,7 @@ public class RawDexFile extends DexBackedDexFile.Impl {
builder.put(ItemType.ANNOTATION_SET_ITEM, AnnotationSetItem.getAnnotator());
builder.put(ItemType.ANNOTATION_ITEM, AnnotationItem.getAnnotator());
builder.put(ItemType.CLASS_DATA_ITEM, ClassDataItem.getAnnotator());
+ builder.put(ItemType.CODE_ITEM, CodeItem.getAnnotator());
annotators = builder.build();
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/util/ReferenceUtil.java b/dexlib2/src/main/java/org/jf/dexlib2/util/ReferenceUtil.java
index 76e6cedf..6b52a3e5 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/util/ReferenceUtil.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/util/ReferenceUtil.java
@@ -31,9 +31,10 @@
package org.jf.dexlib2.util;
-import org.jf.dexlib2.iface.reference.FieldReference;
-import org.jf.dexlib2.iface.reference.MethodReference;
+import org.jf.dexlib2.iface.reference.*;
+import org.jf.util.StringUtils;
+import javax.annotation.Nullable;
import java.io.IOException;
import java.io.Writer;
@@ -93,5 +94,22 @@ public final class ReferenceUtil {
writer.write(fieldReference.getType());
}
+ @Nullable
+ public static String getReferenceString(Reference reference) {
+ if (reference instanceof StringReference) {
+ return String.format("\"%s\"", StringUtils.escapeString(((StringReference)reference).getString()));
+ }
+ if (reference instanceof TypeReference) {
+ return ((TypeReference)reference).getType();
+ }
+ if (reference instanceof FieldReference) {
+ return getFieldDescriptor((FieldReference)reference);
+ }
+ if (reference instanceof MethodReference) {
+ return getMethodDescriptor((MethodReference)reference);
+ }
+ return null;
+ }
+
private ReferenceUtil() {}
}
diff --git a/util/src/main/java/org/jf/util/NumberUtils.java b/util/src/main/java/org/jf/util/NumberUtils.java
new file mode 100644
index 00000000..401de928
--- /dev/null
+++ b/util/src/main/java/org/jf/util/NumberUtils.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2013, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.util;
+
+import java.text.DecimalFormat;
+
+public class NumberUtils {
+ private static final int canonicalFloatNaN = Float.floatToRawIntBits(Float.NaN);
+ private static final int maxFloat = Float.floatToRawIntBits(Float.MAX_VALUE);
+ private static final int piFloat = Float.floatToRawIntBits((float)Math.PI);
+ private static final int eFloat = Float.floatToRawIntBits((float)Math.E);
+
+ private static final long canonicalDoubleNaN = Double.doubleToRawLongBits(Double.NaN);
+ private static final long maxDouble = Double.doubleToLongBits(Double.MAX_VALUE);
+ private static final long piDouble = Double.doubleToLongBits(Math.PI);
+ private static final long eDouble = Double.doubleToLongBits(Math.E);
+
+ private static final DecimalFormat format = new DecimalFormat("0.####################E0");
+
+ public static boolean isLikelyFloat(int value) {
+ // Check for some common named float values
+ // We don't check for Float.MIN_VALUE, which has an integer representation of 1
+ if (value == canonicalFloatNaN ||
+ value == maxFloat ||
+ value == piFloat ||
+ value == eFloat) {
+ return true;
+ }
+
+ // Check for some named integer values
+ if (value == Integer.MAX_VALUE || value == Integer.MIN_VALUE) {
+ return false;
+ }
+
+
+ // Check for likely resource id
+ int packageId = value >> 24;
+ int resourceType = value >> 16 & 0xff;
+ int resourceId = value & 0xffff;
+ if ((packageId == 0x7f || packageId == 1) && resourceType < 0x1f && resourceId < 0xfff) {
+ return false;
+ }
+
+ // a non-canocical NaN is more likely to be an integer
+ float floatValue = Float.intBitsToFloat(value);
+ if (Float.isNaN(floatValue)) {
+ return false;
+ }
+
+ // Otherwise, whichever has a shorter scientific notation representation is more likely.
+ // Integer wins the tie
+ String asInt = format.format(value);
+ String asFloat = format.format(floatValue);
+
+ // try to strip off any small imprecision near the end of the mantissa
+ int decimalPoint = asFloat.indexOf('.');
+ int exponent = asFloat.indexOf("E");
+ int zeros = asFloat.indexOf("000");
+ if (zeros > decimalPoint && zeros < exponent) {
+ asFloat = asFloat.substring(0, zeros) + asFloat.substring(exponent);
+ } else {
+ int nines = asFloat.indexOf("999");
+ if (nines > decimalPoint && nines < exponent) {
+ asFloat = asFloat.substring(0, nines) + asFloat.substring(exponent);
+ }
+ }
+
+ return asFloat.length() < asInt.length();
+ }
+
+ public static boolean isLikelyDouble(long value) {
+ // Check for some common named double values
+ // We don't check for Double.MIN_VALUE, which has a long representation of 1
+ if (value == canonicalDoubleNaN ||
+ value == maxDouble ||
+ value == piDouble ||
+ value == eDouble) {
+ return true;
+ }
+
+ // Check for some named long values
+ if (value == Long.MAX_VALUE || value == Long.MIN_VALUE) {
+ return false;
+ }
+
+ // a non-canocical NaN is more likely to be an long
+ double doubleValue = Double.longBitsToDouble(value);
+ if (Double.isNaN(doubleValue)) {
+ return false;
+ }
+
+ // Otherwise, whichever has a shorter scientific notation representation is more likely.
+ // Long wins the tie
+ String asLong = format.format(value);
+ String asDouble = format.format(doubleValue);
+
+ // try to strip off any small imprecision near the end of the mantissa
+ int decimalPoint = asDouble.indexOf('.');
+ int exponent = asDouble.indexOf("E");
+ int zeros = asDouble.indexOf("000");
+ if (zeros > decimalPoint && zeros < exponent) {
+ asDouble = asDouble.substring(0, zeros) + asDouble.substring(exponent);
+ } else {
+ int nines = asDouble.indexOf("999");
+ if (nines > decimalPoint && nines < exponent) {
+ asDouble = asDouble.substring(0, nines) + asDouble.substring(exponent);
+ }
+ }
+
+ return asDouble.length() < asLong.length();
+ }
+}
diff --git a/util/src/test/java/org/jf/util/NumberUtilsTest.java b/util/src/test/java/org/jf/util/NumberUtilsTest.java
new file mode 100644
index 00000000..a059c87d
--- /dev/null
+++ b/util/src/test/java/org/jf/util/NumberUtilsTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2013, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.util;
+
+import junit.framework.Assert;
+import org.junit.Test;
+
+public class NumberUtilsTest {
+ @Test
+ public void isLikelyFloatTest() {
+ Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits(1.23f)));
+ Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits(1.0f)));
+ Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits(Float.NaN)));
+ Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits(Float.NEGATIVE_INFINITY)));
+ Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits(Float.POSITIVE_INFINITY)));
+ Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits(1e-30f)));
+ Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits(1000f)));
+ Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits(1f)));
+ Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits(-1f)));
+ Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits(-5f)));
+ Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits(1.3333f)));
+ Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits(4.5f)));
+ Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits(.1f)));
+ Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits(50000f)));
+ Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits(Float.MAX_VALUE)));
+ Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits((float)Math.PI)));
+ Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits((float)Math.E)));
+
+ Assert.assertTrue(NumberUtils.isLikelyFloat(2139095039));
+
+
+ // Float.MIN_VALUE is equivalent to integer value 1 - this should be detected as an integer
+ //Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits(Float.MIN_VALUE)));
+
+ // This one doesn't quite work. It has a series of 2 0's, but that is probably not enough to strip off normally
+ //Assert.assertTrue(NumberUtils.isLikelyFloat(Float.floatToRawIntBits(1.33333f)));
+
+ Assert.assertFalse(NumberUtils.isLikelyFloat(0));
+ Assert.assertFalse(NumberUtils.isLikelyFloat(1));
+ Assert.assertFalse(NumberUtils.isLikelyFloat(10));
+ Assert.assertFalse(NumberUtils.isLikelyFloat(100));
+ Assert.assertFalse(NumberUtils.isLikelyFloat(1000));
+ Assert.assertFalse(NumberUtils.isLikelyFloat(1024));
+ Assert.assertFalse(NumberUtils.isLikelyFloat(1234));
+ Assert.assertFalse(NumberUtils.isLikelyFloat(-5));
+ Assert.assertFalse(NumberUtils.isLikelyFloat(-13));
+ Assert.assertFalse(NumberUtils.isLikelyFloat(-123));
+ Assert.assertFalse(NumberUtils.isLikelyFloat(20000000));
+ Assert.assertFalse(NumberUtils.isLikelyFloat(2000000000));
+ Assert.assertFalse(NumberUtils.isLikelyFloat(-2000000000));
+ Assert.assertFalse(NumberUtils.isLikelyFloat(Integer.MAX_VALUE));
+ Assert.assertFalse(NumberUtils.isLikelyFloat(Integer.MIN_VALUE));
+ Assert.assertFalse(NumberUtils.isLikelyFloat(Short.MIN_VALUE));
+ Assert.assertFalse(NumberUtils.isLikelyFloat(Short.MAX_VALUE));
+ }
+
+ @Test
+ public void isLikelyDoubleTest() {
+ Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(1.23f)));
+ Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(1.0f)));
+ Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(Double.NaN)));
+ Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(Double.NEGATIVE_INFINITY)));
+ Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(Double.POSITIVE_INFINITY)));
+ Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(1e-30f)));
+ Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(1000f)));
+ Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(1f)));
+ Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(-1f)));
+ Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(-5f)));
+ Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(1.3333f)));
+ Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(1.33333f)));
+ Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(4.5f)));
+ Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(.1f)));
+ Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(50000f)));
+ Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(Double.MAX_VALUE)));
+ Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(Math.PI)));
+ Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(Math.E)));
+
+ // Double.MIN_VALUE is equivalent to integer value 1 - this should be detected as an integer
+ //Assert.assertTrue(NumberUtils.isLikelyDouble(Double.doubleToRawLongBits(Double.MIN_VALUE)));
+
+ Assert.assertFalse(NumberUtils.isLikelyDouble(0));
+ Assert.assertFalse(NumberUtils.isLikelyDouble(1));
+ Assert.assertFalse(NumberUtils.isLikelyDouble(10));
+ Assert.assertFalse(NumberUtils.isLikelyDouble(100));
+ Assert.assertFalse(NumberUtils.isLikelyDouble(1000));
+ Assert.assertFalse(NumberUtils.isLikelyDouble(1024));
+ Assert.assertFalse(NumberUtils.isLikelyDouble(1234));
+ Assert.assertFalse(NumberUtils.isLikelyDouble(-5));
+ Assert.assertFalse(NumberUtils.isLikelyDouble(-13));
+ Assert.assertFalse(NumberUtils.isLikelyDouble(-123));
+ Assert.assertFalse(NumberUtils.isLikelyDouble(20000000));
+ Assert.assertFalse(NumberUtils.isLikelyDouble(2000000000));
+ Assert.assertFalse(NumberUtils.isLikelyDouble(-2000000000));
+ Assert.assertFalse(NumberUtils.isLikelyDouble(Integer.MAX_VALUE));
+ Assert.assertFalse(NumberUtils.isLikelyDouble(Integer.MIN_VALUE));
+ Assert.assertFalse(NumberUtils.isLikelyDouble(Short.MIN_VALUE));
+ Assert.assertFalse(NumberUtils.isLikelyDouble(Short.MAX_VALUE));
+ }
+}