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