blob: efcc80ba9072a4e46407b033ccf408c4b4ce092d [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.dx.cf.direct;
import com.android.dx.cf.code.ByteOps;
import com.android.dx.cf.code.BytecodeArray;
import com.android.dx.cf.code.SwitchList;
import com.android.dx.cf.iface.ParseObserver;
import com.android.dx.rop.cst.Constant;
import com.android.dx.rop.cst.CstDouble;
import com.android.dx.rop.cst.CstFloat;
import com.android.dx.rop.cst.CstInteger;
import com.android.dx.rop.cst.CstKnownNull;
import com.android.dx.rop.cst.CstLong;
import com.android.dx.rop.cst.CstType;
import com.android.dx.rop.type.Type;
import com.android.dx.util.ByteArray;
import com.android.dx.util.Hex;
import com.android.dx.util.IntList;
import java.util.List;
import java.util.ArrayList;
/**
* Bytecode visitor to use when "observing" bytecode getting parsed.
*/
public class CodeObserver implements BytecodeArray.Visitor {
/** {@code non-null;} actual array of bytecode */
private final ByteArray bytes;
/** {@code non-null;} observer to inform of parsing */
private final ParseObserver observer;
/**
* Constructs an instance.
*
* @param bytes {@code non-null;} actual array of bytecode
* @param observer {@code non-null;} observer to inform of parsing
*/
public CodeObserver(ByteArray bytes, ParseObserver observer) {
if (bytes == null) {
throw new NullPointerException("bytes == null");
}
if (observer == null) {
throw new NullPointerException("observer == null");
}
this.bytes = bytes;
this.observer = observer;
}
/** {@inheritDoc} */
public void visitInvalid(int opcode, int offset, int length) {
observer.parsed(bytes, offset, length, header(offset));
}
/** {@inheritDoc} */
public void visitNoArgs(int opcode, int offset, int length, Type type) {
observer.parsed(bytes, offset, length, header(offset));
}
/** {@inheritDoc} */
public void visitLocal(int opcode, int offset, int length,
int idx, Type type, int value) {
String idxStr = (length <= 3) ? Hex.u1(idx) : Hex.u2(idx);
boolean argComment = (length == 1);
String valueStr = "";
if (opcode == ByteOps.IINC) {
valueStr = ", #" +
((length <= 3) ? Hex.s1(value) : Hex.s2(value));
}
String catStr = "";
if (type.isCategory2()) {
catStr = (argComment ? "," : " //") + " category-2";
}
observer.parsed(bytes, offset, length,
header(offset) + (argComment ? " // " : " ") +
idxStr + valueStr + catStr);
}
/** {@inheritDoc} */
public void visitConstant(int opcode, int offset, int length,
Constant cst, int value) {
if (cst instanceof CstKnownNull) {
// This is aconst_null.
visitNoArgs(opcode, offset, length, null);
return;
}
if (cst instanceof CstInteger) {
visitLiteralInt(opcode, offset, length, value);
return;
}
if (cst instanceof CstLong) {
visitLiteralLong(opcode, offset, length,
((CstLong) cst).getValue());
return;
}
if (cst instanceof CstFloat) {
visitLiteralFloat(opcode, offset, length,
((CstFloat) cst).getIntBits());
return;
}
if (cst instanceof CstDouble) {
visitLiteralDouble(opcode, offset, length,
((CstDouble) cst).getLongBits());
return;
}
String valueStr = "";
if (value != 0) {
valueStr = ", ";
if (opcode == ByteOps.MULTIANEWARRAY) {
valueStr += Hex.u1(value);
} else {
valueStr += Hex.u2(value);
}
}
observer.parsed(bytes, offset, length,
header(offset) + " " + cst + valueStr);
}
/** {@inheritDoc} */
public void visitBranch(int opcode, int offset, int length,
int target) {
String targetStr = (length <= 3) ? Hex.u2(target) : Hex.u4(target);
observer.parsed(bytes, offset, length,
header(offset) + " " + targetStr);
}
/** {@inheritDoc} */
public void visitSwitch(int opcode, int offset, int length,
SwitchList cases, int padding) {
int sz = cases.size();
StringBuffer sb = new StringBuffer(sz * 20 + 100);
sb.append(header(offset));
if (padding != 0) {
sb.append(" // padding: " + Hex.u4(padding));
}
sb.append('\n');
for (int i = 0; i < sz; i++) {
sb.append(" ");
sb.append(Hex.s4(cases.getValue(i)));
sb.append(": ");
sb.append(Hex.u2(cases.getTarget(i)));
sb.append('\n');
}
sb.append(" default: ");
sb.append(Hex.u2(cases.getDefaultTarget()));
observer.parsed(bytes, offset, length, sb.toString());
}
/** {@inheritDoc} */
public void visitNewarray(int offset, int length, CstType cst,
ArrayList<Constant> intVals) {
String commentOrSpace = (length == 1) ? " // " : " ";
String typeName = cst.getClassType().getComponentType().toHuman();
observer.parsed(bytes, offset, length,
header(offset) + commentOrSpace + typeName);
}
/** {@inheritDoc} */
public void setPreviousOffset(int offset) {
// Do nothing
}
/** {@inheritDoc} */
public int getPreviousOffset() {
return -1;
}
/**
* Helper to produce the first bit of output for each instruction.
*
* @param offset the offset to the start of the instruction
*/
private String header(int offset) {
/*
* Note: This uses the original bytecode, not the
* possibly-transformed one.
*/
int opcode = bytes.getUnsignedByte(offset);
String name = ByteOps.opName(opcode);
if (opcode == ByteOps.WIDE) {
opcode = bytes.getUnsignedByte(offset + 1);
name += " " + ByteOps.opName(opcode);
}
return Hex.u2(offset) + ": " + name;
}
/**
* Helper for {@link #visitConstant} where the constant is an
* {@code int}.
*
* @param opcode the opcode
* @param offset offset to the instruction
* @param length instruction length
* @param value constant value
*/
private void visitLiteralInt(int opcode, int offset, int length,
int value) {
String commentOrSpace = (length == 1) ? " // " : " ";
String valueStr;
opcode = bytes.getUnsignedByte(offset); // Compare with orig op below.
if ((length == 1) || (opcode == ByteOps.BIPUSH)) {
valueStr = "#" + Hex.s1(value);
} else if (opcode == ByteOps.SIPUSH) {
valueStr = "#" + Hex.s2(value);
} else {
valueStr = "#" + Hex.s4(value);
}
observer.parsed(bytes, offset, length,
header(offset) + commentOrSpace + valueStr);
}
/**
* Helper for {@link #visitConstant} where the constant is a
* {@code long}.
*
* @param opcode the opcode
* @param offset offset to the instruction
* @param length instruction length
* @param value constant value
*/
private void visitLiteralLong(int opcode, int offset, int length,
long value) {
String commentOrLit = (length == 1) ? " // " : " #";
String valueStr;
if (length == 1) {
valueStr = Hex.s1((int) value);
} else {
valueStr = Hex.s8(value);
}
observer.parsed(bytes, offset, length,
header(offset) + commentOrLit + valueStr);
}
/**
* Helper for {@link #visitConstant} where the constant is a
* {@code float}.
*
* @param opcode the opcode
* @param offset offset to the instruction
* @param length instruction length
* @param bits constant value, as float-bits
*/
private void visitLiteralFloat(int opcode, int offset, int length,
int bits) {
String optArg = (length != 1) ? " #" + Hex.u4(bits) : "";
observer.parsed(bytes, offset, length,
header(offset) + optArg + " // " +
Float.intBitsToFloat(bits));
}
/**
* Helper for {@link #visitConstant} where the constant is a
* {@code double}.
*
* @param opcode the opcode
* @param offset offset to the instruction
* @param length instruction length
* @param bits constant value, as double-bits
*/
private void visitLiteralDouble(int opcode, int offset, int length,
long bits) {
String optArg = (length != 1) ? " #" + Hex.u8(bits) : "";
observer.parsed(bytes, offset, length,
header(offset) + optArg + " // " +
Double.longBitsToDouble(bits));
}
}