aboutsummaryrefslogtreecommitdiff
path: root/dexlib2
diff options
context:
space:
mode:
authorBen Gruver <bgruv@google.com>2021-02-23 19:00:34 -0800
committerBen Gruver <bgruv@google.com>2021-02-26 17:19:59 -0800
commite894435e9d40fa7133f099591cbaf6f233429331 (patch)
tree0b1fbecbeb043372c69b2a73280d860f11ca023d /dexlib2
parent812bf35149975b15186506f28feecf35dc23128c (diff)
downloadgoogle-smali-e894435e9d40fa7133f099591cbaf6f233429331.tar.gz
Add new DexFormatter/DexFormattedWriter class
This will be a more unified way to get/write out human-readable strings for individual "things" from a dex file. The goal is to replace things like ReferenceUtil, EncodedValueUtils, etc.
Diffstat (limited to 'dexlib2')
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/formatter/DexFormattedWriter.java445
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/formatter/DexFormatter.java166
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/util/EncodedValueUtils.java7
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/util/ReferenceUtil.java6
-rw-r--r--dexlib2/src/main/java/org/jf/util/StringUtils.java4
-rw-r--r--dexlib2/src/test/java/org/jf/dexlib2/formatter/DexFormattedWriterStringTest.java83
-rw-r--r--dexlib2/src/test/java/org/jf/dexlib2/formatter/DexFormattedWriterTest.java466
-rw-r--r--dexlib2/src/test/java/org/jf/dexlib2/formatter/DexFormattedWriterTypeTest.java101
-rw-r--r--dexlib2/src/test/java/org/jf/dexlib2/formatter/DexFormatterTest.java186
9 files changed, 1464 insertions, 0 deletions
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/formatter/DexFormattedWriter.java b/dexlib2/src/main/java/org/jf/dexlib2/formatter/DexFormattedWriter.java
new file mode 100644
index 00000000..951a0da8
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/formatter/DexFormattedWriter.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright 2021, 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.formatter;
+
+import org.jf.dexlib2.MethodHandleType;
+import org.jf.dexlib2.ValueType;
+import org.jf.dexlib2.iface.AnnotationElement;
+import org.jf.dexlib2.iface.reference.*;
+import org.jf.dexlib2.iface.value.*;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Set;
+
+/**
+ * This class handles formatting and writing various types of items in a dex file to a Writer.
+ */
+public class DexFormattedWriter extends Writer {
+
+ protected final Writer writer;
+
+ public DexFormattedWriter(Writer writer) {
+ this.writer = writer;
+ }
+
+ /**
+ * Write the method descriptor for the given {@link MethodReference}.
+ */
+ public void writeMethodDescriptor(MethodReference methodReference) throws IOException {
+ writeType(methodReference.getDefiningClass());
+ writer.write("->");
+ writeSimpleName(methodReference.getName());
+ writer.write('(');
+ for (CharSequence paramType: methodReference.getParameterTypes()) {
+ writeType(paramType);
+ }
+ writer.write(')');
+ writeType(methodReference.getReturnType());
+ }
+
+ /**
+ * Write the short method descriptor for the given {@link MethodReference}.
+ *
+ * <p>The short method descriptor elides the class that the field is a member of.
+ */
+ public void writeShortMethodDescriptor(MethodReference methodReference) throws IOException {
+ writeSimpleName(methodReference.getName());
+ writer.write('(');
+ for (CharSequence paramType: methodReference.getParameterTypes()) {
+ writeType(paramType);
+ }
+ writer.write(')');
+ writeType(methodReference.getReturnType());
+ }
+
+ /**
+ * Write the method proto descriptor for the given {@link MethodProtoReference}.
+ */
+ public void writeMethodProtoDescriptor(MethodProtoReference protoReference) throws IOException {
+ writer.write('(');
+ for (CharSequence paramType : protoReference.getParameterTypes()) {
+ writeType(paramType);
+ }
+ writer.write(')');
+ writeType(protoReference.getReturnType());
+ }
+
+ /**
+ * Write the field descriptor for the given {@link FieldReference}.
+ */
+ public void writeFieldDescriptor(FieldReference fieldReference) throws IOException {
+ writeType(fieldReference.getDefiningClass());
+ writer.write("->");
+ writeSimpleName(fieldReference.getName());
+ writer.write(':');
+ writeType(fieldReference.getType());
+ }
+
+ /**
+ * Write the short field descriptor for the given {@link FieldReference}.
+ *
+ * <p>The short field descriptor typically elides the class that the field is a member of.
+ */
+ public void writeShortFieldDescriptor(FieldReference fieldReference) throws IOException {
+ writeSimpleName(fieldReference.getName());
+ writer.write(':');
+ writeType(fieldReference.getType());
+ }
+
+ /**
+ * Write the given {@link MethodHandleReference}.
+ */
+ public void writeMethodHandle(MethodHandleReference methodHandleReference) throws IOException {
+ writer.write(MethodHandleType.toString(methodHandleReference.getMethodHandleType()));
+ writer.write('@');
+
+ Reference memberReference = methodHandleReference.getMemberReference();
+ if (memberReference instanceof MethodReference) {
+ writeMethodDescriptor((MethodReference)memberReference);
+ } else {
+ writeFieldDescriptor((FieldReference)memberReference);
+ }
+ }
+
+ /**
+ * Write the given {@link CallSiteReference}.
+ */
+ public void writeCallSite(CallSiteReference callSiteReference) throws IOException {
+ writeSimpleName(callSiteReference.getName());
+ writer.write('(');
+ writeQuotedString(callSiteReference.getMethodName());
+ writer.write(", ");
+ writeMethodProtoDescriptor(callSiteReference.getMethodProto());
+
+ for (EncodedValue encodedValue : callSiteReference.getExtraArguments()) {
+ writer.write(", ");
+ writeEncodedValue(encodedValue);
+ }
+ writer.write(")@");
+ MethodHandleReference methodHandle = callSiteReference.getMethodHandle();
+ if (methodHandle.getMethodHandleType() != MethodHandleType.INVOKE_STATIC) {
+ throw new IllegalArgumentException("The linker method handle for a call site must be of type invoke-static");
+ }
+ writeMethodDescriptor((MethodReference)callSiteReference.getMethodHandle().getMemberReference());
+ }
+
+ /**
+ * Write the given type.
+ */
+ public void writeType(CharSequence type) throws IOException {
+ for (int i = 0; i < type.length(); i++) {
+ char c = type.charAt(i);
+ if (c == 'L') {
+ writeClass(type.subSequence(i, type.length()));
+ return;
+ } else if (c == '[') {
+ writer.write(c);
+ } else if (c == 'Z' ||
+ c == 'B' ||
+ c == 'S' ||
+ c == 'C' ||
+ c == 'I' ||
+ c == 'J' ||
+ c == 'F' ||
+ c == 'D' ||
+ c == 'V') {
+ writer.write(c);
+
+ if (i != type.length() - 1) {
+ throw new IllegalArgumentException(
+ String.format("Invalid type string: %s", type));
+ }
+ return;
+ } else {
+ throw new IllegalArgumentException(
+ String.format("Invalid type string: %s", type));
+ }
+ }
+
+ // Any valid type would have returned from within the loop.
+ throw new IllegalArgumentException(
+ String.format("Invalid type string: %s", type));
+ }
+
+ protected void writeClass(CharSequence type) throws IOException {
+ assert type.charAt(0) == 'L';
+
+ writer.write(type.charAt(0));
+
+ int startIndex = 1;
+ int i;
+ for (i = startIndex; i < type.length(); i++) {
+ char c = type.charAt(i);
+
+ if (c == '/') {
+ if (i == startIndex) {
+ throw new IllegalArgumentException(
+ String.format("Invalid type string: %s", type));
+ }
+
+ writeSimpleName(type.subSequence(startIndex, i));
+ writer.write(type.charAt(i));
+ startIndex = i+1;
+ } else if (c == ';') {
+ if (i == startIndex) {
+ throw new IllegalArgumentException(
+ String.format("Invalid type string: %s", type));
+ }
+
+ writeSimpleName(type.subSequence(startIndex, i));
+ writer.write(type.charAt(i));
+ break;
+ }
+ }
+
+ if (i != type.length() - 1 || type.charAt(i) != ';') {
+ throw new IllegalArgumentException(
+ String.format("Invalid type string: %s", type));
+ }
+ }
+
+ /**
+ * Writes the given simple name.
+ *
+ * @param simpleName The <a href="https://source.android.com/devices/tech/dalvik/dex-format#simplename">simple name</a>
+ * to write.
+ */
+ protected void writeSimpleName(CharSequence simpleName) throws IOException {
+ writer.append(simpleName);
+ }
+
+ /**
+ * Write the given quoted string.
+ *
+ * <p>This includes the beginning and ending quotation marks, and the string value is be escaped as necessary.
+ */
+ public void writeQuotedString(CharSequence charSequence) throws IOException {
+ writer.write('"');
+
+ String string = charSequence.toString();
+ for (int i = 0; i < string.length(); i++) {
+ char c = string.charAt(i);
+
+ if ((c >= ' ') && (c < 0x7f)) {
+ if ((c == '\'') || (c == '\"') || (c == '\\')) {
+ writer.write('\\');
+ }
+ writer.write(c);
+ continue;
+ } else if (c <= 0x7f) {
+ switch (c) {
+ case '\n': writer.write("\\n"); continue;
+ case '\r': writer.write("\\r"); continue;
+ case '\t': writer.write("\\t"); continue;
+ }
+ }
+
+ writer.write("\\u");
+ writer.write(Character.forDigit(c >> 12, 16));
+ writer.write(Character.forDigit((c >> 8) & 0x0f, 16));
+ writer.write(Character.forDigit((c >> 4) & 0x0f, 16));
+ writer.write(Character.forDigit(c & 0x0f, 16));
+ }
+
+ writer.write('"');
+ }
+
+ /**
+ * Write the given {@link EncodedValue}.
+ */
+ public void writeEncodedValue(EncodedValue encodedValue) throws IOException {
+ switch (encodedValue.getValueType()) {
+ case ValueType.BOOLEAN:
+ writer.write(Boolean.toString(((BooleanEncodedValue) encodedValue).getValue()));
+ break;
+ case ValueType.BYTE:
+ writer.write(
+ String.format("0x%x", ((ByteEncodedValue)encodedValue).getValue()));
+ break;
+ case ValueType.CHAR:
+ writer.write(
+ String.format("0x%x", (int)((CharEncodedValue)encodedValue).getValue()));
+ break;
+ case ValueType.SHORT:
+ writer.write(
+ String.format("0x%x", ((ShortEncodedValue)encodedValue).getValue()));
+ break;
+ case ValueType.INT:
+ writer.write(
+ String.format("0x%x", ((IntEncodedValue)encodedValue).getValue()));
+ break;
+ case ValueType.LONG:
+ writer.write(
+ String.format("0x%x", ((LongEncodedValue)encodedValue).getValue()));
+ break;
+ case ValueType.FLOAT:
+ writer.write(Float.toString(((FloatEncodedValue)encodedValue).getValue()));
+ break;
+ case ValueType.DOUBLE:
+ writer.write(Double.toString(((DoubleEncodedValue)encodedValue).getValue()));
+ break;
+ case ValueType.ANNOTATION:
+ writeAnnotation((AnnotationEncodedValue)encodedValue);
+ break;
+ case ValueType.ARRAY:
+ writeArray((ArrayEncodedValue)encodedValue);
+ break;
+ case ValueType.STRING:
+ writeQuotedString(((StringEncodedValue)encodedValue).getValue());
+ break;
+ case ValueType.FIELD:
+ writeFieldDescriptor(((FieldEncodedValue)encodedValue).getValue());
+ break;
+ case ValueType.ENUM:
+ writeFieldDescriptor(((EnumEncodedValue)encodedValue).getValue());
+ break;
+ case ValueType.METHOD:
+ writeMethodDescriptor(((MethodEncodedValue)encodedValue).getValue());
+ break;
+ case ValueType.TYPE:
+ writeType(((TypeEncodedValue)encodedValue).getValue());
+ break;
+ case ValueType.METHOD_TYPE:
+ writeMethodProtoDescriptor(((MethodTypeEncodedValue)encodedValue).getValue());
+ break;
+ case ValueType.METHOD_HANDLE:
+ writeMethodHandle(((MethodHandleEncodedValue)encodedValue).getValue());
+ break;
+ case ValueType.NULL:
+ writer.write("null");
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown encoded value type");
+ }
+ }
+
+ /**
+ * Write the given {@link AnnotationEncodedValue}.
+ */
+ protected void writeAnnotation(AnnotationEncodedValue annotation) throws IOException {
+ writer.write("Annotation[");
+ writeType(annotation.getType());
+
+ Set<? extends AnnotationElement> elements = annotation.getElements();
+ for (AnnotationElement element: elements) {
+ writer.write(", ");
+ writeSimpleName(element.getName());
+ writer.write('=');
+ writeEncodedValue(element.getValue());
+ }
+
+ writer.write(']');
+ }
+
+ /**
+ * Write the given {@link ArrayEncodedValue}.
+ */
+ protected void writeArray(ArrayEncodedValue array) throws IOException {
+ writer.write("Array[");
+
+ boolean first = true;
+ for (EncodedValue element: array.getValue()) {
+ if (first) {
+ first = false;
+ } else {
+ writer.write(", ");
+ }
+ writeEncodedValue(element);
+ }
+
+ writer.write(']');
+ }
+
+ /**
+ * Write the given {@link Reference}.
+ */
+ public void writeReference(Reference reference) throws IOException {
+ if (reference instanceof StringReference) {
+ writeQuotedString((StringReference) reference);
+ } else if (reference instanceof TypeReference) {
+ writeType((TypeReference) reference);
+ } else if (reference instanceof FieldReference) {
+ writeFieldDescriptor((FieldReference) reference);
+ } else if (reference instanceof MethodReference) {
+ writeMethodDescriptor((MethodReference) reference);
+ } else if (reference instanceof MethodProtoReference) {
+ writeMethodProtoDescriptor((MethodProtoReference) reference);
+ } else if (reference instanceof MethodHandleReference) {
+ writeMethodHandle((MethodHandleReference) reference);
+ } else if (reference instanceof CallSiteReference) {
+ writeCallSite((CallSiteReference) reference);
+ } else {
+ throw new IllegalArgumentException(String.format("Not a known reference type: %s", reference.getClass()));
+ }
+ }
+
+ @Override public void write(int c) throws IOException {
+ writer.write(c);
+ }
+
+ @Override public void write(char[] cbuf) throws IOException {
+ writer.write(cbuf);
+ }
+
+ @Override public void write(char[] cbuf, int off, int len) throws IOException {
+ writer.write(cbuf, off, len);
+ }
+
+ @Override public void write(String str) throws IOException {
+ writer.write(str);
+ }
+
+ @Override public void write(String str, int off, int len) throws IOException {
+ writer.write(str, off, len);
+ }
+
+ @Override public Writer append(CharSequence csq) throws IOException {
+ return writer.append(csq);
+ }
+
+ @Override public Writer append(CharSequence csq, int start, int end) throws IOException {
+ return writer.append(csq, start, end);
+ }
+
+ @Override public Writer append(char c) throws IOException {
+ return writer.append(c);
+ }
+
+ @Override public void flush() throws IOException {
+ writer.flush();
+ }
+
+ @Override public void close() throws IOException {
+ writer.close();
+ }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/formatter/DexFormatter.java b/dexlib2/src/main/java/org/jf/dexlib2/formatter/DexFormatter.java
new file mode 100644
index 00000000..47233b55
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/formatter/DexFormatter.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2021, 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.formatter;
+
+import org.jf.dexlib2.iface.reference.*;
+import org.jf.dexlib2.iface.value.EncodedValue;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+
+/**
+ * This class handles formatting and getting strings for various types of items in a dex file.
+ */
+public class DexFormatter {
+
+ public static final DexFormatter INSTANCE = new DexFormatter();
+
+ /**
+ * Gets a {@link DexFormattedWriter} for writing formatted strings to a {@link Writer}, with the same settings as this Formatter.
+ *
+ * @param writer The {@link Writer} that the {@link DexFormattedWriter} will write to.
+ */
+ public DexFormattedWriter getWriter(Writer writer) {
+ return new DexFormattedWriter(writer);
+ }
+
+ public String getMethodDescriptor(MethodReference methodReference) {
+ StringWriter writer = new StringWriter();
+ try {
+ getWriter(writer).writeMethodDescriptor(methodReference);
+ } catch (IOException e) {
+ throw new AssertionError("Unexpected IOException");
+ }
+ return writer.toString();
+ }
+
+ public String getShortMethodDescriptor(MethodReference methodReference) {
+ StringWriter writer = new StringWriter();
+ try {
+ getWriter(writer).writeShortMethodDescriptor(methodReference);
+ } catch (IOException e) {
+ throw new AssertionError("Unexpected IOException");
+ }
+ return writer.toString();
+ }
+
+ public String getMethodProtoDescriptor(MethodProtoReference protoReference) {
+ StringWriter writer = new StringWriter();
+ try {
+ getWriter(writer).writeMethodProtoDescriptor(protoReference);
+ } catch (IOException e) {
+ throw new AssertionError("Unexpected IOException");
+ }
+ return writer.toString();
+ }
+
+ public String getFieldDescriptor(FieldReference fieldReference) {
+ StringWriter writer = new StringWriter();
+ try {
+ getWriter(writer).writeFieldDescriptor(fieldReference);
+ } catch (IOException e) {
+ throw new AssertionError("Unexpected IOException");
+ }
+ return writer.toString();
+ }
+
+ public String getShortFieldDescriptor(FieldReference fieldReference) {
+ StringWriter writer = new StringWriter();
+ try {
+ getWriter(writer).writeShortFieldDescriptor(fieldReference);
+ } catch (IOException e) {
+ throw new AssertionError("Unexpected IOException");
+ }
+ return writer.toString();
+ }
+
+ public String getMethodHandle(MethodHandleReference methodHandleReference) {
+ StringWriter writer = new StringWriter();
+ try {
+ getWriter(writer).writeMethodHandle(methodHandleReference);
+ } catch (IOException e) {
+ throw new AssertionError("Unexpected IOException");
+ }
+ return writer.toString();
+ }
+
+ public String getCallSite(CallSiteReference callSiteReference) {
+ StringWriter writer = new StringWriter();
+ try {
+ getWriter(writer).writeCallSite(callSiteReference);
+ } catch (IOException e) {
+ throw new AssertionError("Unexpected IOException");
+ }
+ return writer.toString();
+ }
+
+ public String getType(CharSequence type) {
+ StringWriter writer = new StringWriter();
+ try {
+ getWriter(writer).writeType(type);
+ } catch (IOException e) {
+ throw new AssertionError("Unexpected IOException");
+ }
+ return writer.toString();
+ }
+
+ public String getQuotedString(CharSequence string) {
+ StringWriter writer = new StringWriter();
+ try {
+ getWriter(writer).writeQuotedString(string);
+ } catch (IOException e) {
+ throw new AssertionError("Unexpected IOException");
+ }
+ return writer.toString();
+ }
+
+ public String getEncodedValue(EncodedValue encodedValue) {
+ StringWriter writer = new StringWriter();
+ try {
+ getWriter(writer).writeEncodedValue(encodedValue);
+ } catch (IOException e) {
+ throw new AssertionError("Unexpected IOException");
+ }
+ return writer.toString();
+ }
+
+ public String getReference(Reference reference) {
+ StringWriter writer = new StringWriter();
+ try {
+ getWriter(writer).writeReference(reference);
+ } catch (IOException e) {
+ throw new AssertionError("Unexpected IOException");
+ }
+ return writer.toString();
+ }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/util/EncodedValueUtils.java b/dexlib2/src/main/java/org/jf/dexlib2/util/EncodedValueUtils.java
index a09bbca2..26062584 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/util/EncodedValueUtils.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/util/EncodedValueUtils.java
@@ -40,6 +40,9 @@ import java.io.IOException;
import java.io.Writer;
import java.util.Set;
+/**
+ * Some utilities for generating human-readable strings for encoded values.
+ */
public final class EncodedValueUtils {
public static boolean isDefaultValue(EncodedValue encodedValue) {
switch (encodedValue.getValueType()) {
@@ -65,6 +68,10 @@ public final class EncodedValueUtils {
return false;
}
+ /**
+ * @deprecated use {@link org.jf.dexlib2.formatter.DefaultDexFormatter} instead.
+ */
+ @Deprecated
public static void writeEncodedValue(Writer writer, EncodedValue encodedValue) throws IOException {
switch (encodedValue.getValueType()) {
case ValueType.BOOLEAN:
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 86d35368..d33fb263 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/util/ReferenceUtil.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/util/ReferenceUtil.java
@@ -42,6 +42,12 @@ import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
+/**
+ * Some utilities for generating human-readable strings for the various types of references.
+ *
+ * @deprecated use {@link org.jf.dexlib2.formatter.DefaultDexFormatter} instead.
+ */
+@Deprecated
public final class ReferenceUtil {
public static String getMethodDescriptor(MethodReference methodReference) {
return getMethodDescriptor(methodReference, false);
diff --git a/dexlib2/src/main/java/org/jf/util/StringUtils.java b/dexlib2/src/main/java/org/jf/util/StringUtils.java
index 4de6d9de..01fcb8f9 100644
--- a/dexlib2/src/main/java/org/jf/util/StringUtils.java
+++ b/dexlib2/src/main/java/org/jf/util/StringUtils.java
@@ -57,6 +57,10 @@ public class StringUtils {
writer.write(Character.forDigit(c & 0x0f, 16));
}
+ /**
+ * @deprecated Use {@link org.jf.dexlib2.formatter.DexFormattedWriter#writeQuotedString(CharSequence)}
+ */
+ @Deprecated
public static void writeEscapedString(Writer writer, String value) throws IOException {
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
diff --git a/dexlib2/src/test/java/org/jf/dexlib2/formatter/DexFormattedWriterStringTest.java b/dexlib2/src/test/java/org/jf/dexlib2/formatter/DexFormattedWriterStringTest.java
new file mode 100644
index 00000000..73b55a5c
--- /dev/null
+++ b/dexlib2/src/test/java/org/jf/dexlib2/formatter/DexFormattedWriterStringTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2021, 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.formatter;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+public class DexFormattedWriterStringTest {
+
+ @Test
+ public void testWriteQuotedString() throws IOException {
+ Assert.assertEquals(
+ "\"string value\"",
+ performWriteQuotedString("string value"));
+
+ Assert.assertEquals(
+ "\"string value with double quote \\\"\"",
+ performWriteQuotedString("string value with double quote \""));
+
+ Assert.assertEquals(
+ "\"string value with single quote \\'\"",
+ performWriteQuotedString("string value with single quote '"));
+
+ Assert.assertEquals(
+ "\"string value with backslash \\\\\"",
+ performWriteQuotedString("string value with backslash \\"));
+
+ Assert.assertEquals(
+ "\"string value with newline \\n\"",
+ performWriteQuotedString("string value with newline \n"));
+
+ Assert.assertEquals(
+ "\"string value with return \\r\"",
+ performWriteQuotedString("string value with return \r"));
+
+ Assert.assertEquals(
+ "\"string value with tab \\t\"",
+ performWriteQuotedString("string value with tab \t"));
+
+ Assert.assertEquals(
+ "\"string value with unicode escape \\u1234\"",
+ performWriteQuotedString("string value with unicode escape \u1234"));
+ }
+
+ private String performWriteQuotedString(String value) throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ DexFormattedWriter writer = new DexFormattedWriter(stringWriter);
+ writer.writeQuotedString(value);
+ return stringWriter.toString();
+ }
+}
diff --git a/dexlib2/src/test/java/org/jf/dexlib2/formatter/DexFormattedWriterTest.java b/dexlib2/src/test/java/org/jf/dexlib2/formatter/DexFormattedWriterTest.java
new file mode 100644
index 00000000..4ac2e96a
--- /dev/null
+++ b/dexlib2/src/test/java/org/jf/dexlib2/formatter/DexFormattedWriterTest.java
@@ -0,0 +1,466 @@
+/*
+ * Copyright 2021, 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.formatter;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.jf.dexlib2.MethodHandleType;
+import org.jf.dexlib2.iface.reference.CallSiteReference;
+import org.jf.dexlib2.iface.reference.MethodHandleReference;
+import org.jf.dexlib2.immutable.ImmutableAnnotationElement;
+import org.jf.dexlib2.immutable.reference.*;
+import org.jf.dexlib2.immutable.value.*;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+public class DexFormattedWriterTest {
+
+ private StringWriter output;
+
+ @Before
+ public void setup() {
+ output = new StringWriter();
+ }
+
+ @Test
+ public void testWriteMethodDescriptor() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeMethodDescriptor(getMethodReference());
+
+ Assert.assertEquals("Ldefining/class;->methodName(Lparam1;Lparam2;)Lreturn/type;", output.toString());
+ }
+
+ @Test
+ public void testWriteShortMethodDescriptor() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeShortMethodDescriptor(getMethodReference());
+
+ Assert.assertEquals("methodName(Lparam1;Lparam2;)Lreturn/type;", output.toString());
+ }
+
+ @Test
+ public void testWriteMethodProtoDescriptor() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeMethodProtoDescriptor(getMethodProtoReference());
+
+ Assert.assertEquals("(Lparam1;Lparam2;)Lreturn/type;", output.toString());
+ }
+
+ @Test
+ public void testWriteFieldDescriptor() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeFieldDescriptor(getFieldReference());
+
+ Assert.assertEquals("Ldefining/class;->fieldName:Lfield/type;", output.toString());
+ }
+
+ @Test
+ public void testWriteShortFieldDescriptor() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeShortFieldDescriptor(getFieldReference());
+
+ Assert.assertEquals("fieldName:Lfield/type;", output.toString());
+ }
+
+ @Test
+ public void testWriteMethodHandle_fieldAccess() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeMethodHandle(getMethodHandleReferenceForField());
+
+ Assert.assertEquals("instance-get@Ldefining/class;->fieldName:Lfield/type;", output.toString());
+ }
+
+ @Test
+ public void testWriteMethodHandle_methodAccess() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeMethodHandle(getMethodHandleReferenceForMethod());
+
+ Assert.assertEquals("invoke-instance@Ldefining/class;->methodName(Lparam1;Lparam2;)Lreturn/type;",
+ output.toString());
+ }
+
+ @Test
+ public void testWriteCallsite() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeCallSite(getCallSiteReference());
+
+ Assert.assertEquals(
+ "callsiteName(\"callSiteMethodName\", " +
+ "(Lparam1;Lparam2;)Lreturn/type;, Ldefining/class;->fieldName:Lfield/type;, " +
+ "Ldefining/class;->methodName(Lparam1;Lparam2;)Lreturn/type;)@" +
+ "Ldefining/class;->methodName(Lparam1;Lparam2;)Lreturn/type;",
+ output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_boolean_true() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(ImmutableBooleanEncodedValue.TRUE_VALUE);
+
+ Assert.assertEquals("true", output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_boolean_false() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(ImmutableBooleanEncodedValue.FALSE_VALUE);
+
+ Assert.assertEquals("false", output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_byte() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(new ImmutableByteEncodedValue((byte)0x12));
+
+ Assert.assertEquals("0x12", output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_char() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(new ImmutableCharEncodedValue('a'));
+
+ Assert.assertEquals("0x61", output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_short() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(new ImmutableShortEncodedValue((short) 0x12));
+
+ Assert.assertEquals("0x12", output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_int() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(new ImmutableIntEncodedValue(0x12));
+
+ Assert.assertEquals("0x12", output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_long() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(new ImmutableLongEncodedValue(0x12));
+
+ Assert.assertEquals("0x12", output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_float() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(new ImmutableFloatEncodedValue(12.34f));
+
+ Assert.assertEquals("12.34", output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_double() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(new ImmutableDoubleEncodedValue(12.34));
+
+ Assert.assertEquals("12.34", output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_annotation() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(new ImmutableAnnotationEncodedValue(
+ "Lannotation/type;",
+ ImmutableSet.of(
+ new ImmutableAnnotationElement("element1", new ImmutableFieldEncodedValue(getFieldReference())),
+ new ImmutableAnnotationElement("element2", new ImmutableMethodEncodedValue(getMethodReference()))
+ )));
+
+ Assert.assertEquals(
+ "Annotation[Lannotation/type;, " +
+ "element1=Ldefining/class;->fieldName:Lfield/type;, " +
+ "element2=Ldefining/class;->methodName(Lparam1;Lparam2;)Lreturn/type;]",
+ output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_array() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(new ImmutableArrayEncodedValue(ImmutableList.of(
+ new ImmutableFieldEncodedValue(getFieldReference()),
+ new ImmutableMethodEncodedValue(getMethodReference()))));
+
+ Assert.assertEquals(
+ "Array[Ldefining/class;->fieldName:Lfield/type;, " +
+ "Ldefining/class;->methodName(Lparam1;Lparam2;)Lreturn/type;]",
+ output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_string() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(new ImmutableStringEncodedValue("string value\n"));
+
+ Assert.assertEquals(
+ "\"string value\\n\"",
+ output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_field() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(new ImmutableFieldEncodedValue(getFieldReference()));
+
+ Assert.assertEquals(
+ "Ldefining/class;->fieldName:Lfield/type;",
+ output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_enum() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(new ImmutableEnumEncodedValue(getFieldReference()));
+
+ Assert.assertEquals(
+ "Ldefining/class;->fieldName:Lfield/type;",
+ output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_method() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(new ImmutableMethodEncodedValue(getMethodReference()));
+
+ Assert.assertEquals(
+ "Ldefining/class;->methodName(Lparam1;Lparam2;)Lreturn/type;",
+ output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_type() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(new ImmutableTypeEncodedValue("Ltest/type;"));
+
+ Assert.assertEquals(
+ "Ltest/type;",
+ output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_methodType() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(new ImmutableMethodTypeEncodedValue(getMethodProtoReference()));
+
+ Assert.assertEquals(
+ "(Lparam1;Lparam2;)Lreturn/type;",
+ output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_methodHandle() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(new ImmutableMethodHandleEncodedValue(getMethodHandleReferenceForField()));
+
+ Assert.assertEquals(
+ "instance-get@Ldefining/class;->fieldName:Lfield/type;",
+ output.toString());
+ }
+
+ @Test
+ public void testWriteEncodedValue_null() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeEncodedValue(ImmutableNullEncodedValue.INSTANCE);
+
+ Assert.assertEquals(
+ "null",
+ output.toString());
+ }
+
+ @Test
+ public void testWriteReference_string() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeReference(new ImmutableStringReference("string value"));
+
+ Assert.assertEquals(
+ "\"string value\"",
+ output.toString());
+ }
+
+ @Test
+ public void testWriteReference_type() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeReference(new ImmutableTypeReference("Ltest/type;"));
+
+ Assert.assertEquals(
+ "Ltest/type;",
+ output.toString());
+ }
+
+ @Test
+ public void testWriteReference_field() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeReference(getFieldReference());
+
+ Assert.assertEquals(
+ "Ldefining/class;->fieldName:Lfield/type;",
+ output.toString());
+ }
+
+ @Test
+ public void testWriteReference_method() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeReference(getMethodReference());
+
+ Assert.assertEquals(
+ "Ldefining/class;->methodName(Lparam1;Lparam2;)Lreturn/type;",
+ output.toString());
+ }
+
+ @Test
+ public void testWriteReference_methodProto() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeReference(getMethodProtoReference());
+
+ Assert.assertEquals(
+ "(Lparam1;Lparam2;)Lreturn/type;",
+ output.toString());
+ }
+
+ @Test
+ public void testWriteReference_methodHandle() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeReference(getMethodHandleReferenceForMethod());
+
+ Assert.assertEquals(
+ "invoke-instance@Ldefining/class;->methodName(Lparam1;Lparam2;)Lreturn/type;",
+ output.toString());
+ }
+
+ @Test
+ public void testWriteReference_callSite() throws IOException {
+ DexFormattedWriter writer = new DexFormattedWriter(output);
+
+ writer.writeReference(getCallSiteReference());
+
+ Assert.assertEquals(
+ "callsiteName(\"callSiteMethodName\", " +
+ "(Lparam1;Lparam2;)Lreturn/type;, Ldefining/class;->fieldName:Lfield/type;, " +
+ "Ldefining/class;->methodName(Lparam1;Lparam2;)Lreturn/type;)@" +
+ "Ldefining/class;->methodName(Lparam1;Lparam2;)Lreturn/type;",
+ output.toString());
+ }
+
+ private ImmutableMethodReference getMethodReference() {
+ return new ImmutableMethodReference(
+ "Ldefining/class;",
+ "methodName",
+ ImmutableList.of("Lparam1;", "Lparam2;"),
+ "Lreturn/type;");
+ }
+
+ private ImmutableMethodProtoReference getMethodProtoReference() {
+ return new ImmutableMethodProtoReference(
+ ImmutableList.of("Lparam1;", "Lparam2;"),
+ "Lreturn/type;");
+ }
+
+ private ImmutableFieldReference getFieldReference() {
+ return new ImmutableFieldReference(
+ "Ldefining/class;",
+ "fieldName",
+ "Lfield/type;");
+ }
+
+ private ImmutableMethodHandleReference getMethodHandleReferenceForField() {
+ return new ImmutableMethodHandleReference(
+ MethodHandleType.INSTANCE_GET,
+ getFieldReference());
+ }
+
+ private MethodHandleReference getMethodHandleReferenceForMethod() {
+ return new ImmutableMethodHandleReference(
+ MethodHandleType.INVOKE_INSTANCE,
+ getMethodReference());
+ }
+
+ private MethodHandleReference getInvokeStaticMethodHandleReferenceForMethod() {
+ return new ImmutableMethodHandleReference(
+ MethodHandleType.INVOKE_STATIC,
+ getMethodReference());
+ }
+
+ private CallSiteReference getCallSiteReference() {
+ return new ImmutableCallSiteReference(
+ "callsiteName",
+ getInvokeStaticMethodHandleReferenceForMethod(),
+ "callSiteMethodName",
+ getMethodProtoReference(),
+ ImmutableList.of(
+ new ImmutableFieldEncodedValue(getFieldReference()),
+ new ImmutableMethodEncodedValue(getMethodReference())));
+ }
+}
diff --git a/dexlib2/src/test/java/org/jf/dexlib2/formatter/DexFormattedWriterTypeTest.java b/dexlib2/src/test/java/org/jf/dexlib2/formatter/DexFormattedWriterTypeTest.java
new file mode 100644
index 00000000..2f55e717
--- /dev/null
+++ b/dexlib2/src/test/java/org/jf/dexlib2/formatter/DexFormattedWriterTypeTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2021, 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.formatter;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+public class DexFormattedWriterTypeTest {
+
+
+ @Test
+ public void testWriteType_unquoted() throws IOException {
+ String[] typeStrings = new String[] {
+ "Ljava/lang/Object;",
+ "Z",
+ "B",
+ "S",
+ "C",
+ "I",
+ "J",
+ "F",
+ "D",
+ "V",
+ "[D",
+ "[[D",
+ "[Ljava/lang/Object;",
+ "[[Ljava/lang/Object;",
+ "LC;"
+ };
+
+ for (String typeString: typeStrings) {
+ Assert.assertEquals(typeString, performWriteType(typeString));
+ }
+ }
+
+ @Test
+ public void testWriteType_invalid() throws IOException {
+
+ assertWriteTypeFails("L;");
+ assertWriteTypeFails("H");
+ assertWriteTypeFails("L/blah;");
+ assertWriteTypeFails("La//b;");
+
+ assertWriteTypeFails("La//b");
+ assertWriteTypeFails("La//b ");
+
+ assertWriteTypeFails("[");
+
+ assertWriteTypeFails("[L");
+
+ assertWriteTypeFails("[L ");
+ }
+
+ private void assertWriteTypeFails(String input) throws IOException {
+ try {
+ performWriteType(input);
+ Assert.fail("Expected failure did not occur");
+ } catch (IllegalArgumentException ex) {
+ // expected exception
+ }
+ }
+
+ private String performWriteType(String input) throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ DexFormattedWriter writer = new DexFormattedWriter(stringWriter);
+ writer.writeType(input);
+ return stringWriter.toString();
+ }
+}
diff --git a/dexlib2/src/test/java/org/jf/dexlib2/formatter/DexFormatterTest.java b/dexlib2/src/test/java/org/jf/dexlib2/formatter/DexFormatterTest.java
new file mode 100644
index 00000000..cad6c779
--- /dev/null
+++ b/dexlib2/src/test/java/org/jf/dexlib2/formatter/DexFormatterTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2021, 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.formatter;
+
+import org.jf.dexlib2.iface.reference.*;
+import org.jf.dexlib2.iface.value.EncodedValue;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import static org.mockito.Mockito.mock;
+
+public class DexFormatterTest {
+
+ @Test
+ public void testGetMethodReference() throws IOException {
+ TestDexFormatter formatter = new TestDexFormatter();
+ Assert.assertEquals(
+ "method descriptor",
+ formatter.getMethodDescriptor(mock(MethodReference.class)));
+ }
+
+ @Test
+ public void testGetShortMethodReference() throws IOException {
+ TestDexFormatter formatter = new TestDexFormatter();
+ Assert.assertEquals(
+ "short method descriptor",
+ formatter.getShortMethodDescriptor(mock(MethodReference.class)));
+ }
+
+ @Test
+ public void testGetMethodProtoReference() throws IOException {
+ TestDexFormatter formatter = new TestDexFormatter();
+ Assert.assertEquals(
+ "method proto descriptor",
+ formatter.getMethodProtoDescriptor(mock(MethodProtoReference.class)));
+ }
+
+ @Test
+ public void testGetFieldReference() throws IOException {
+ TestDexFormatter formatter = new TestDexFormatter();
+ Assert.assertEquals(
+ "field descriptor",
+ formatter.getFieldDescriptor(mock(FieldReference.class)));
+ }
+
+ @Test
+ public void testGetShortFieldReference() throws IOException {
+ TestDexFormatter formatter = new TestDexFormatter();
+ Assert.assertEquals(
+ "short field descriptor",
+ formatter.getShortFieldDescriptor(mock(FieldReference.class)));
+ }
+
+ @Test
+ public void testGetMethodHandle() throws IOException {
+ TestDexFormatter formatter = new TestDexFormatter();
+ Assert.assertEquals(
+ "method handle",
+ formatter.getMethodHandle(mock(MethodHandleReference.class)));
+ }
+
+ @Test
+ public void testGetCallSite() throws IOException {
+ TestDexFormatter formatter = new TestDexFormatter();
+ Assert.assertEquals(
+ "call site",
+ formatter.getCallSite(mock(CallSiteReference.class)));
+ }
+
+ @Test
+ public void testGetType() throws IOException {
+ TestDexFormatter formatter = new TestDexFormatter();
+ Assert.assertEquals(
+ "type",
+ formatter.getType("mock type"));
+ }
+
+ @Test
+ public void testGetQuotedString() throws IOException {
+ TestDexFormatter formatter = new TestDexFormatter();
+ Assert.assertEquals(
+ "quoted string",
+ formatter.getQuotedString("mock string"));
+ }
+
+ @Test
+ public void testGetEncodedValue() throws IOException {
+ TestDexFormatter formatter = new TestDexFormatter();
+ Assert.assertEquals(
+ "encoded value",
+ formatter.getEncodedValue(mock(EncodedValue.class)));
+ }
+
+ @Test
+ public void testReference() throws IOException {
+ TestDexFormatter formatter = new TestDexFormatter();
+ Assert.assertEquals(
+ "reference",
+ formatter.getReference(mock(Reference.class)));
+ }
+
+ private static class TestDexFormatter extends DexFormatter {
+
+ @Override public DexFormattedWriter getWriter(Writer writer) {
+ return new DexFormattedWriter(writer) {
+ @Override public void writeMethodDescriptor(MethodReference methodReference) throws IOException {
+ writer.write("method descriptor");
+ }
+
+ @Override public void writeShortMethodDescriptor(MethodReference methodReference) throws IOException {
+ writer.write("short method descriptor");
+ }
+
+ @Override
+ public void writeMethodProtoDescriptor(MethodProtoReference protoReference) throws IOException {
+ writer.write("method proto descriptor");
+ }
+
+ @Override public void writeFieldDescriptor(FieldReference fieldReference) throws IOException {
+ writer.write("field descriptor");
+ }
+
+ @Override public void writeShortFieldDescriptor(FieldReference fieldReference) throws IOException {
+ writer.write("short field descriptor");
+ }
+
+ @Override
+ public void writeMethodHandle(MethodHandleReference methodHandleReference) throws IOException {
+ writer.write("method handle");
+ }
+
+ @Override public void writeCallSite(CallSiteReference callSiteReference) throws IOException {
+ writer.write("call site");
+ }
+
+ @Override public void writeType(CharSequence type) throws IOException {
+ writer.write("type");
+ }
+
+ @Override public void writeQuotedString(CharSequence string) throws IOException {
+ writer.write("quoted string");
+ }
+
+ @Override public void writeEncodedValue(EncodedValue encodedValue) throws IOException {
+ writer.write("encoded value");
+ }
+
+ @Override public void writeReference(Reference reference) throws IOException {
+ writer.write("reference");
+ }
+ };
+ }
+ }
+}