Java tutorial
/** * JReversePro - Java Decompiler / Disassembler. * Copyright (C) 2008 Karthik Kumar. * * 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 org.jreversepro.decompile.simulate; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import org.apache.commons.io.IOUtils; import org.jreversepro.CustomLoggerFactory; import org.jreversepro.ast.expression.Expression; import org.jreversepro.jls.JLSConstants; import org.jreversepro.jvm.Opcodes; import org.jreversepro.jvm.TypeInferrer; import org.jreversepro.reflect.Method; import org.jreversepro.reflect.instruction.Instruction; public class SwitchTable { /** * @param method * Reference to current method. * @param ins * Instruction that corresponds to a tableswitch or a lookupswitch * instruction. * @param gotos * Map of goto statements. * * @throws IOException * thrown in case of any error. * @throws RevEngineException * if the instruction passed is not a switch opcode. */ public SwitchTable(Method method, Instruction ins, Map<Integer, Integer> gotos) throws IOException { this.method = method; insIndex = ins.currentPc; cases = new ArrayList<CaseEntry>(); if (ins.opcode == Opcodes.OPCODE_TABLESWITCH) { createTableSwitch(ins.args, ins.currentPc, gotos); } else if (ins.opcode == Opcodes.OPCODE_LOOKUPSWITCH) { createLookupSwitch(ins.args, ins.currentPc, gotos); } else { throw new IllegalArgumentException("Opcode " + ins.opcode + " Not a switch statement"); } datatype = null; } /** * @param method * Reference to current method. * @param ins * Instruction that corresponds to a tableswitch or a lookupswitch * instruction. * @param op1 * Operand that is to be used inside the switch statement. * @param gotos * Map of goto statements. * @throws IOException * thrown in case of any error. * @throws RevEngineException * if the instruction passed is not a switch opcode. */ public SwitchTable(Method method, Instruction ins, Expression op1, Map<Integer, Integer> gotos) throws IOException { this.datatype = op1.getType(); this.varName = op1.getJLSCode(); // Copy - paste from prev. constructor. this.method = method; insIndex = ins.currentPc; cases = new ArrayList<CaseEntry>(); if (ins.opcode == Opcodes.OPCODE_TABLESWITCH) { createTableSwitch(ins.args, ins.currentPc, gotos); } else if (ins.opcode == Opcodes.OPCODE_LOOKUPSWITCH) { createLookupSwitch(ins.args, ins.currentPc, gotos); } else { throw new IllegalArgumentException("Opcode " + ins.opcode + " Not a switch statement"); } logger.fine("switch datatype " + datatype); } /** * @return Returns the default byte of this switch block. */ public int getDefaultByte() { return defaultByte; } /** * For 'tableswitch' opcode this fills the data structure - JSwitchTable. * * @param entries * Bytecode entries that contain the case values and their target * opcodes. * @param offset * offset is the index of the current tableswitch instruction into * the method bytecode array. * @param gotos * Map of goto statements. * @throws IOException * Thrown in case of an i/o error when reading from the bytes. */ private void createTableSwitch(byte[] entries, int offset, Map<Integer, Integer> gotos) throws IOException { DataInputStream dis = null; try { dis = new DataInputStream(new ByteArrayInputStream(entries)); defaultByte = dis.readInt() + offset; int lowVal = dis.readInt(); int highVal = dis.readInt(); Map<Integer, CaseEntry> mapCases = new HashMap<Integer, CaseEntry>(); for (int i = lowVal; i <= highVal; i++) { int curTarget = dis.readInt() + offset; String value = TypeInferrer.getValue(String.valueOf(i), this.datatype); CaseEntry ent = mapCases.get(Integer.valueOf(curTarget)); if (ent == null) { mapCases.put(Integer.valueOf(curTarget), new CaseEntry(value, curTarget)); } else { ent.addValue(value); } } cases = new ArrayList<CaseEntry>(mapCases.values()); } finally { IOUtils.closeQuietly(dis); } processData(gotos); } /** * For 'lookupswitch' opcode this fills the data structure - JSwitchTable. * * @param entries * Bytecode entries that contain the case values and their target * opcodes. * @param offset * offset is the index of the current lookupswitch instruction into * the method bytecode array. * @param gotos * Map of goto statements. * * @throws IOException * Thrown in case of an i/o error when reading from the bytes. */ private void createLookupSwitch(byte[] entries, int offset, Map<Integer, Integer> gotos) throws IOException { DataInputStream dis = null; try { dis = new DataInputStream(new ByteArrayInputStream(entries)); defaultByte = dis.readInt() + offset; int numVal = dis.readInt(); Map<Integer, CaseEntry> mapCases = new HashMap<Integer, CaseEntry>(); for (int i = 0; i < numVal; i++) { String value = TypeInferrer.getValue(String.valueOf(dis.readInt()), datatype); int curTarget = dis.readInt() + offset; CaseEntry ent = mapCases.get(Integer.valueOf(curTarget)); if (ent == null) { mapCases.put(Integer.valueOf(curTarget), new CaseEntry(value, curTarget)); } else { ent.addValue(value); } } cases = new ArrayList<CaseEntry>(mapCases.values()); } finally { IOUtils.closeQuietly(dis); } processData(gotos); } /** * @return Returns the list of cases. Individual elements are JCaseEntry. */ public List<CaseEntry> getCases() { return cases; } /** * @return Returns the disassembled string for this switch statement block. */ public String disassemble() { StringBuilder sb = new StringBuilder(""); for (int i = 0; i < cases.size(); i++) { sb.append("\n\t\t\t" + cases.get(i)); } sb.append("\n\t\t\tDefault Byte " + defaultByte); return sb.toString(); } /** * @param rhsType * Type of the switch variable of this block. * @param rhsValue * Value ( name ) of the switch variable for this block. */ public void setTypeValue(String rhsType, String rhsValue) { varName = rhsValue; datatype = rhsType; // dataType could be either int or char. } /** * Process the bytecode stream of case values and targets to individual case * blocks. * * @param gotos * Map of goto statements. */ public void processData(Map<Integer, Integer> gotos) { maxTarget = defaultByte; if (gotos != null) { for (CaseEntry ent : cases) { Integer obj = gotos.get(Integer.valueOf(ent.getTarget() - 3)); if (obj != null) { int tempVal = obj.intValue(); maxTarget = (maxTarget > tempVal) ? maxTarget : tempVal; } } if (maxTarget > defaultByte) { boolean targetPresent = false; for (int i = 0; i < cases.size() - 1; i++) { CaseEntry ent = cases.get(i); if (ent.getTarget() == defaultByte) { ent.addValue(JLSConstants.DEFAULT); // ent.setTarget(defa); targetPresent = true; } } if (!targetPresent) { cases.add(new CaseEntry(JLSConstants.DEFAULT, defaultByte)); } } } // Sort the entries Collections.sort(cases, new CaseComparator()); // Assign endTargets for all of them. int i = 0; for (; i < cases.size() - 1; i++) { CaseEntry ent = cases.get(i); CaseEntry entNext = cases.get(i + 1); ent.setEndTarget(entNext.getTarget()); } CaseEntry entLast = cases.get(i); entLast.setEndTarget(maxTarget); } /** * @return Returns a branch entry for this switch statement. */ public BranchEntry getBranchEntry() { return new BranchEntry(method, insIndex, maxTarget, maxTarget, BranchConstants.TYPE_SWITCH, varName, "", ""); } /** * Inserts a CaseEntry in the list. * * @param caseEntry * Case Entry to be appended. */ public void addCaseEntry(CaseEntry caseEntry) { cases.add(caseEntry); } /** * @return Returns the Stringified version of this class. */ @Override public String toString() { return cases.toString(); } /** * Instruction index of the switch statement. */ int insIndex; /** * List of cases that are available. Individual elements are JCaseEntry. */ List<CaseEntry> cases; /** * DefaultByte for this switch statement. */ int defaultByte; /** Max Target from this switch statement group * */ int maxTarget; /** * Name of the variable that is put under switch statement. */ String varName; /** * Datatype of the variable for which switch is used. */ String datatype; /** * Reference to method to which this switch table belongs. */ Method method; private final Logger logger = CustomLoggerFactory.createLogger(); }