All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jf.baksmali.formatter.BaksmaliWriter Maven / Gradle / Ivy

/*
 * 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;

import static java.lang.Math.abs;


/**
 * A specialized version of DexFormattedWriter that handles quoting
 * simple names 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.
     *
     * 

The simple name will be quoted with backticks if quoted is true * *

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: https://source.android.com/devices/tech/dalvik/dex-format#simplename */ 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"); writeUnsignedLongAsHex(-value); } else { writer.write("0x"); writeUnsignedLongAsHex(value); } if (suffix != null) { writer.write(suffix); } } protected void writeCharEncodedValue(CharEncodedValue encodedValue) throws IOException { char c = encodedValue.getValue(); if ((c >= ' ') && (c < 0x7f)) { writer.write('\''); if ((c == '\'') || (c == '\"') || (c == '\\')) { writer.write('\\'); } writer.write(c); writer.write('\''); 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('\''); 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 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 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 writeUnsignedLongAsHex(long value) throws IOException { int bufferIndex = 23; do { int digit = (int)(value & 15); if (digit < 10) { buffer[bufferIndex--] = (char)(digit + '0'); } else { buffer[bufferIndex--] = (char)((digit - 10) + 'a'); } value >>>= 4; } while (value != 0); bufferIndex++; write(buffer, bufferIndex, 24-bufferIndex); } public void writeSignedLongAsDec(long value) throws IOException { int bufferIndex = 23; if (value < 0) { write('-'); } do { long digit = abs(value % 10); buffer[bufferIndex--] = (char)(digit + '0'); value = value / 10; } while (value != 0); bufferIndex++; write(buffer, bufferIndex, 24-bufferIndex); } public void writeSignedIntAsDec(int value) throws IOException { int bufferIndex = 15; if (value < 0) { write('-'); } do { int digit = abs(value % 10); buffer[bufferIndex--] = (char)(digit + '0'); value = value / 10; } while (value != 0); bufferIndex++; write(buffer, bufferIndex, 16-bufferIndex); } public void writeUnsignedIntAsDec(int value) throws IOException { if (value < 0) { writeSignedLongAsDec(value & 0xFFFFFFFFL); } else { writeSignedIntAsDec(value); } } public void writeSignedIntOrLongTo(long val) throws IOException { if (val<0) { writer.write("-0x"); writeUnsignedLongAsHex(-val); if (val < Integer.MIN_VALUE) { writer.write('L'); } } else { writer.write("0x"); writeUnsignedLongAsHex(val); if (val > Integer.MAX_VALUE) { writer.write('L'); } } } public void indent(int indentAmount) { ((IndentingWriter) writer).indent(indentAmount); } public void deindent(int indentAmount) { ((IndentingWriter) writer).deindent(indentAmount); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy