mirror of
https://github.com/revanced/smali.git
synced 2025-04-29 22:24:26 +02:00
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:
parent
c2ac11c693
commit
b6a1ae3481
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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 == '\\')) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user