Add new BaksmaliWriter/BaksmaliFormatter classes

These are intended to be the centralized place for most individual items
to be formatted and converted to/written as text
This commit is contained in:
Ben Gruver 2021-02-26 11:21:33 -08:00
parent c2ac11c693
commit b6a1ae3481
5 changed files with 884 additions and 0 deletions

View File

@ -0,0 +1,55 @@
/*
* 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.baksmali.formatter;
import org.jf.dexlib2.formatter.DexFormatter;
import javax.annotation.Nullable;
import java.io.Writer;
public class BaksmaliFormatter extends DexFormatter {
@Nullable private final String classContext;
public BaksmaliFormatter() {
this(null);
}
public BaksmaliFormatter(@Nullable String classContext) {
this.classContext = classContext;
}
@Override
public BaksmaliWriter getWriter(Writer writer) {
return new BaksmaliWriter(writer, classContext);
}
}

View File

@ -0,0 +1,368 @@
/*
* 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.baksmali.formatter;
import org.jf.dexlib2.MethodHandleType;
import org.jf.dexlib2.ValueType;
import org.jf.dexlib2.formatter.DexFormattedWriter;
import org.jf.dexlib2.iface.AnnotationElement;
import org.jf.dexlib2.iface.reference.CallSiteReference;
import org.jf.dexlib2.iface.reference.FieldReference;
import org.jf.dexlib2.iface.reference.MethodHandleReference;
import org.jf.dexlib2.iface.reference.MethodReference;
import org.jf.dexlib2.iface.value.*;
import org.jf.util.IndentingWriter;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.Writer;
import java.util.Collection;
/**
* A specialized version of DexFormattedWriter that handles quoting
* <a href="https://source.android.com/devices/tech/dalvik/dex-format#simplename">simple names</a> containing spaces.
*/
public class BaksmaliWriter extends DexFormattedWriter {
@Nullable private final String classContext;
protected final char[] buffer = new char[24];
public BaksmaliWriter(Writer writer) {
this(writer, null);
}
/**
* Constructs a new BaksmaliWriter
*
* @param writer The {@link IndentingWriter} to write to
* @param classContext If provided, the class will be elided from any field/method descriptors whose containing
* class match this instance's classContext.
*/
public BaksmaliWriter(Writer writer, @Nullable String classContext) {
super(writer instanceof IndentingWriter ? writer : new IndentingWriter(writer));
this.classContext = classContext;
}
@Override public void writeMethodDescriptor(MethodReference methodReference) throws IOException {
if (methodReference.getDefiningClass().equals(classContext)) {
writeShortMethodDescriptor(methodReference);
} else {
super.writeMethodDescriptor(methodReference);
}
}
@Override public void writeFieldDescriptor(FieldReference fieldReference) throws IOException {
if (fieldReference.getDefiningClass().equals(classContext)) {
writeShortFieldDescriptor(fieldReference);
} else {
super.writeFieldDescriptor(fieldReference);
}
}
@Override
protected void writeClass(CharSequence type) throws IOException {
assert type.charAt(0) == 'L';
writer.write(type.charAt(0));
int startIndex = 1;
boolean hasSpace = false;
int i;
for (i = startIndex; i < type.length(); i++) {
char c = type.charAt(i);
if (Character.getType(c) == Character.SPACE_SEPARATOR) {
hasSpace = true;
} else if (c == '/') {
if (i == startIndex) {
throw new IllegalArgumentException(
String.format("Invalid type string: %s", type));
}
writeSimpleName(type.subSequence(startIndex, i), hasSpace);
writer.write(type.charAt(i));
hasSpace = false;
startIndex = i+1;
} else if (c == ';') {
if (i == startIndex) {
throw new IllegalArgumentException(
String.format("Invalid type string: %s", type));
}
writeSimpleName(type.subSequence(startIndex, i), hasSpace);
writer.write(type.charAt(i));
break;
}
}
if (i != type.length() - 1 || type.charAt(i) != ';') {
throw new IllegalArgumentException(
String.format("Invalid type string: %s", type));
}
}
@Override
public void writeSimpleName(CharSequence simpleName) throws IOException {
boolean hasSpace = false;
for (int i = 0; i < simpleName.length(); i++) {
if (Character.getType(simpleName.charAt(i)) == Character.SPACE_SEPARATOR) {
hasSpace = true;
break;
}
}
writeSimpleName(simpleName, hasSpace);
}
/**
* Writes the given simple name, potentially quoting it if requested.
*
* <p>The simple name will be quoted with backticks if quoted is true
*
* <p>A simple name should typically be quoted if it is meant to be human readable, and it contains spaces.
*
* @param simpleName The simple name to write. See: <a href="https://source.android.com/devices/tech/dalvik/dex-format#simplename">https://source.android.com/devices/tech/dalvik/dex-format#simplename</a>
*/
public void writeSimpleName(CharSequence simpleName, boolean quoted) throws IOException {
if (quoted) {
writer.write('`');
}
writer.append(simpleName);
if (quoted) {
writer.write('`');
}
}
public void writeEncodedValue(EncodedValue encodedValue) throws IOException {
switch (encodedValue.getValueType()) {
case ValueType.BOOLEAN:
writeBooleanEncodedValue((BooleanEncodedValue) encodedValue);
break;
case ValueType.BYTE:
writeIntegralValue(((ByteEncodedValue) encodedValue).getValue(), 't');
break;
case ValueType.CHAR:
writeCharEncodedValue((CharEncodedValue) encodedValue);
break;
case ValueType.SHORT:
writeIntegralValue(((ShortEncodedValue) encodedValue).getValue(), 's');
break;
case ValueType.INT:
writeIntegralValue(((IntEncodedValue) encodedValue).getValue(), null);
break;
case ValueType.LONG:
writeIntegralValue(((LongEncodedValue)encodedValue).getValue(), 'L');
break;
case ValueType.FLOAT:
writeFloatEncodedValue((FloatEncodedValue) encodedValue);
break;
case ValueType.DOUBLE:
writeDoubleEncodedValue((DoubleEncodedValue) encodedValue);
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:
writeEnum((EnumEncodedValue) encodedValue);
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");
}
}
protected void writeBooleanEncodedValue(BooleanEncodedValue encodedValue) throws IOException {
writer.write(Boolean.toString(encodedValue.getValue()));
}
protected void writeIntegralValue(long value, @Nullable Character suffix) throws IOException {
if (value < 0) {
writer.write("-0x");
indentingWriter().printUnsignedLongAsHex(-value);
} else {
writer.write("0x");
indentingWriter().printUnsignedLongAsHex(value);
}
if (suffix != null) {
writer.write(suffix);
}
}
protected void writeCharEncodedValue(CharEncodedValue encodedValue) throws IOException {
writer.write('\'');
char c = encodedValue.getValue();
if ((c >= ' ') && (c < 0x7f)) {
if ((c == '\'') || (c == '\"') || (c == '\\')) {
writer.write('\\');
}
writer.write(c);
return;
} else if (c <= 0x7f) {
switch (c) {
case '\n': writer.write("\\n"); return;
case '\r': writer.write("\\r"); return;
case '\t': writer.write("\\t"); return;
}
}
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('\'');
}
protected void writeFloatEncodedValue(FloatEncodedValue encodedValue) throws IOException {
writer.write(Float.toString(encodedValue.getValue()));
writer.write('f');
}
protected void writeDoubleEncodedValue(DoubleEncodedValue encodedValue) throws IOException {
writer.write(Double.toString(encodedValue.getValue()));
}
protected void writeEnum(EnumEncodedValue encodedValue) throws IOException {
writer.write(".enum ");
writeFieldDescriptor(encodedValue.getValue());
}
/**
* Write the given {@link AnnotationEncodedValue}.
*/
protected void writeAnnotation(AnnotationEncodedValue annotation) throws IOException {
writer.write(".subannotation ");
writeType(annotation.getType());
writer.write('\n');
writeAnnotationElements(annotation.getElements());
writer.write(".end subannotation");
}
public void writeAnnotationElements(
@Nonnull Collection<? extends AnnotationElement> annotationElements) throws IOException {
indent(4);
for (AnnotationElement annotationElement: annotationElements) {
writeSimpleName(annotationElement.getName());
writer.write(" = ");
writeEncodedValue(annotationElement.getValue());
writer.write('\n');
}
deindent(4);
}
/**
* Write the given {@link ArrayEncodedValue}.
*/
protected void writeArray(ArrayEncodedValue array) throws IOException {
writer.write('{');
Collection<? extends EncodedValue> values = array.getValue();
if (values.size() == 0) {
writer.write('}');
return;
}
writer.write('\n');
indent(4);
boolean first = true;
for (EncodedValue encodedValue: values) {
if (!first) {
writer.write(",\n");
}
first = false;
writeEncodedValue(encodedValue);
}
deindent(4);
writer.write("\n}");
}
@Override 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());
}
public IndentingWriter indentingWriter() {
return (IndentingWriter) writer;
}
public void indent(int indentAmount) {
((IndentingWriter) writer).indent(indentAmount);
}
public void deindent(int indentAmount) {
((IndentingWriter) writer).deindent(indentAmount);
}
}

View File

@ -0,0 +1,322 @@
/*
* 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.baksmali.formatter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.jf.dexlib2.MethodHandleType;
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.jf.util.IndentingWriter;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.StringWriter;
public class BaksmaliWriterTest {
private StringWriter stringWriter;
private IndentingWriter output;
@Before
public void setup() {
stringWriter = new StringWriter();
output = new IndentingWriter(stringWriter);
}
@Test
public void testWriteMethodDescriptor_withSpaces() throws IOException {
BaksmaliWriter writer =
new BaksmaliWriter(output);
writer.writeMethodDescriptor(getMethodReferenceWithSpaces());
Assert.assertEquals(
"Ldefining/class/`with spaces`;->`methodName with spaces`(L`param with spaces 1`;L`param with spaces 2`;)" +
"Lreturn/type/`with spaces`;",
stringWriter.toString());
}
@Test
public void testWriteShortMethodDescriptor_withSpaces() throws IOException {
BaksmaliWriter writer =
new BaksmaliWriter(output);
writer.writeShortMethodDescriptor(getMethodReferenceWithSpaces());
Assert.assertEquals(
"`methodName with spaces`(L`param with spaces 1`;L`param with spaces 2`;)" +
"Lreturn/type/`with spaces`;",
stringWriter.toString());
}
@Test
public void testWriteMethodProtoDescriptor_withSpaces() throws IOException {
BaksmaliWriter writer =
new BaksmaliWriter(output);
writer.writeMethodProtoDescriptor(getMethodProtoReferenceWithSpaces());
Assert.assertEquals(
"(L`param with spaces 1`;L`param with spaces 2`;)Lreturn/type/`with spaces`;",
stringWriter.toString());
}
@Test
public void testWriteFieldDescriptor_withSpaces() throws IOException {
BaksmaliWriter writer =
new BaksmaliWriter(output);
writer.writeFieldDescriptor(getFieldReferenceWithSpaces());
Assert.assertEquals("Ldefining/class/`with spaces`;->`fieldName with spaces`:Lfield/`type with spaces`;",
stringWriter.toString());
}
@Test
public void testWriteShortFieldDescriptor_withSpaces() throws IOException {
BaksmaliWriter writer =
new BaksmaliWriter(output);
writer.writeShortFieldDescriptor(getFieldReferenceWithSpaces());
Assert.assertEquals("`fieldName with spaces`:Lfield/`type with spaces`;",
stringWriter.toString());
}
@Test
public void testWriteMethodHandle_fieldAccess_withSpaces() throws IOException {
BaksmaliWriter writer =
new BaksmaliWriter(output);
writer.writeMethodHandle(getMethodHandleReferenceForFieldWithSpaces());
Assert.assertEquals("instance-get@Ldefining/class/`with spaces`;->`fieldName with spaces`:" +
"Lfield/`type with spaces`;", stringWriter.toString());
}
@Test
public void testWriteMethodHandle_methodAccess_withSpaces() throws IOException {
BaksmaliWriter writer =
new BaksmaliWriter(output);
writer.writeMethodHandle(getMethodHandleReferenceForMethodWithSpaces());
Assert.assertEquals("invoke-instance@Ldefining/class/`with spaces`;->`methodName with spaces`(" +
"L`param with spaces 1`;L`param with spaces 2`;)Lreturn/type/`with spaces`;",
stringWriter.toString());
}
@Test
public void testWriteCallsite_withSpaces() throws IOException {
BaksmaliWriter writer =
new BaksmaliWriter(output);
writer.writeCallSite(new ImmutableCallSiteReference(
"callsiteName with spaces",
getInvokeStaticMethodHandleReferenceForMethodWithSpaces(),
"callSiteMethodName with spaces",
getMethodProtoReferenceWithSpaces(),
ImmutableList.of(
new ImmutableFieldEncodedValue(getFieldReferenceWithSpaces()),
new ImmutableMethodEncodedValue(getMethodReferenceWithSpaces()))));
Assert.assertEquals(
"`callsiteName with spaces`(\"callSiteMethodName with spaces\", " +
"(L`param with spaces 1`;L`param with spaces 2`;)Lreturn/type/`with spaces`;, " +
"Ldefining/class/`with spaces`;->`fieldName with spaces`:Lfield/`type with spaces`;, " +
"Ldefining/class/`with spaces`;->`methodName with spaces`(" +
"L`param with spaces 1`;L`param with spaces 2`;)Lreturn/type/`with spaces`;)@" +
"Ldefining/class/`with spaces`;->`methodName with spaces`(" +
"L`param with spaces 1`;L`param with spaces 2`;)Lreturn/type/`with spaces`;",
stringWriter.toString());
}
@Test
public void testWriteEncodedValue_annotation_withSpaces() throws IOException {
BaksmaliWriter writer =
new BaksmaliWriter(output);
writer.writeEncodedValue(new ImmutableAnnotationEncodedValue(
"Lannotation/type with spaces;",
ImmutableSet.of(
new ImmutableAnnotationElement("element with spaces 1",
new ImmutableFieldEncodedValue(getFieldReferenceWithSpaces())),
new ImmutableAnnotationElement("element with spaces 2",
new ImmutableMethodEncodedValue(getMethodReferenceWithSpaces()))
)));
Assert.assertEquals(
".subannotation Lannotation/`type with spaces`;\n" +
" `element with spaces 1` = Ldefining/class/`with spaces`;->`fieldName with spaces`:Lfield/`type with spaces`;\n" +
" `element with spaces 2` = Ldefining/class/`with spaces`;->`methodName with spaces`(" +
"L`param with spaces 1`;L`param with spaces 2`;)Lreturn/type/`with spaces`;\n" +
".end subannotation",
stringWriter.toString());
}
@Test
public void testWriteEncodedValue_array_withSpaces() throws IOException {
BaksmaliWriter writer =
new BaksmaliWriter(output);
writer.writeEncodedValue(new ImmutableArrayEncodedValue(ImmutableList.of(
new ImmutableFieldEncodedValue(getFieldReferenceWithSpaces()),
new ImmutableMethodEncodedValue(getMethodReferenceWithSpaces()))));
Assert.assertEquals(
"{\n" +
" Ldefining/class/`with spaces`;->`fieldName with spaces`:Lfield/`type with spaces`;,\n" +
" Ldefining/class/`with spaces`;->`methodName with spaces`(L`param with spaces 1`;L`param with spaces 2`;)Lreturn/type/`with spaces`;\n" +
"}",
stringWriter.toString());
}
@Test
public void testWriteEncodedValue_field_withSpaces() throws IOException {
BaksmaliWriter writer =
new BaksmaliWriter(output);
writer.writeEncodedValue(new ImmutableFieldEncodedValue(getFieldReferenceWithSpaces()));
Assert.assertEquals(
"Ldefining/class/`with spaces`;->`fieldName with spaces`:Lfield/`type with spaces`;",
stringWriter.toString());
}
@Test
public void testWriteEncodedValue_enum_withSpaces() throws IOException {
BaksmaliWriter writer =
new BaksmaliWriter(output);
writer.writeEncodedValue(new ImmutableEnumEncodedValue(getFieldReferenceWithSpaces()));
Assert.assertEquals(
".enum Ldefining/class/`with spaces`;->`fieldName with spaces`:Lfield/`type with spaces`;",
stringWriter.toString());
}
@Test
public void testWriteEncodedValue_method_withSpaces() throws IOException {
BaksmaliWriter writer =
new BaksmaliWriter(output);
writer.writeEncodedValue(new ImmutableMethodEncodedValue(getMethodReferenceWithSpaces()));
Assert.assertEquals(
"Ldefining/class/`with spaces`;->`methodName with spaces`(" +
"L`param with spaces 1`;L`param with spaces 2`;)Lreturn/type/`with spaces`;",
stringWriter.toString());
}
@Test
public void testWriteEncodedValue_type_withSpaces() throws IOException {
BaksmaliWriter writer =
new BaksmaliWriter(output);
writer.writeEncodedValue(new ImmutableTypeEncodedValue("Ltest/type with spaces;"));
Assert.assertEquals(
"Ltest/`type with spaces`;",
stringWriter.toString());
}
@Test
public void testWriteEncodedValue_methodType_withSpaces() throws IOException {
BaksmaliWriter writer =
new BaksmaliWriter(output);
writer.writeEncodedValue(new ImmutableMethodTypeEncodedValue(getMethodProtoReferenceWithSpaces()));
Assert.assertEquals(
"(L`param with spaces 1`;L`param with spaces 2`;)Lreturn/type/`with spaces`;",
stringWriter.toString());
}
@Test
public void testWriteEncodedValue_methodHandle_withSpaces() throws IOException {
BaksmaliWriter writer =
new BaksmaliWriter(output);
writer.writeEncodedValue(
new ImmutableMethodHandleEncodedValue(getMethodHandleReferenceForMethodWithSpaces()));
Assert.assertEquals(
"invoke-instance@Ldefining/class/`with spaces`;->`methodName with spaces`(" +
"L`param with spaces 1`;L`param with spaces 2`;)Lreturn/type/`with spaces`;",
stringWriter.toString());
}
private ImmutableMethodReference getMethodReferenceWithSpaces() {
return new ImmutableMethodReference(
"Ldefining/class/with spaces;",
"methodName with spaces",
ImmutableList.of("Lparam with spaces 1;", "Lparam with spaces 2;"),
"Lreturn/type/with spaces;");
}
private ImmutableMethodProtoReference getMethodProtoReferenceWithSpaces() {
return new ImmutableMethodProtoReference(
ImmutableList.of("Lparam with spaces 1;", "Lparam with spaces 2;"),
"Lreturn/type/with spaces;");
}
private ImmutableFieldReference getFieldReferenceWithSpaces() {
return new ImmutableFieldReference(
"Ldefining/class/with spaces;",
"fieldName with spaces",
"Lfield/type with spaces;");
}
private MethodHandleReference getMethodHandleReferenceForFieldWithSpaces() {
return new ImmutableMethodHandleReference(
MethodHandleType.INSTANCE_GET,
getFieldReferenceWithSpaces());
}
private ImmutableMethodHandleReference getMethodHandleReferenceForMethodWithSpaces() {
return new ImmutableMethodHandleReference(
MethodHandleType.INVOKE_INSTANCE,
getMethodReferenceWithSpaces());
}
private MethodHandleReference getInvokeStaticMethodHandleReferenceForMethodWithSpaces() {
return new ImmutableMethodHandleReference(
MethodHandleType.INVOKE_STATIC,
getMethodReferenceWithSpaces());
}
}

View File

@ -0,0 +1,134 @@
/*
* 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.baksmali.formatter;
import com.google.common.collect.Lists;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.io.StringWriter;
import java.util.List;
public class BaksmaliWriterTypeTest {
@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_withSpaces() throws IOException {
Assert.assertEquals("Lmy/`pack age`/`class name`;",
performWriteType("Lmy/pack age/class name;"));
Assert.assertEquals("L` `;", performWriteType("L ;"));
Assert.assertEquals("Lmy/` `/class;", performWriteType("Lmy/ /class;"));
Assert.assertEquals("L` `;", performWriteType("L ;"));
Assert.assertEquals("L` ab`;", performWriteType("L ab;"));
Assert.assertEquals("L`ab `;", performWriteType("Lab ;"));
List<Character> spaceCharacters = Lists.newArrayList(
'\u0020',
'\u00A0',
'\u1680',
'\u202f',
'\u205f',
'\u3000');
for (char c = 0x2000; c <= 0x200a; c++) {
spaceCharacters.add(c);
}
for (char c: spaceCharacters) {
Assert.assertEquals(
String.format("Error while testing character \\u%04x", (int)c),
String.format("Lmy/`%c`/package;", c),
performWriteType(String.format("Lmy/%c/package;", c)));
}
}
@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();
BaksmaliWriter writer = new BaksmaliWriter(stringWriter, null);
writer.writeType(input);
return stringWriter.toString();
}
}

View File

@ -35,6 +35,11 @@ import java.io.IOException;
import java.io.Writer;
public class StringUtils {
/**
* @deprecated Use {@link org.jf.baksmali.formatter.BaksmaliWriter#writeCharEncodedValue}
*/
@Deprecated
public static void writeEscapedChar(Writer writer, char c) throws IOException {
if ((c >= ' ') && (c < 0x7f)) {
if ((c == '\'') || (c == '\"') || (c == '\\')) {