/* SAAF: A static analyzer for APK files. * Copyright (C) 2013 syssec.rub.de * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package de.rub.syssec.saaf.application.methods; import java.util.LinkedList; import de.rub.syssec.saaf.model.application.BasicBlockInterface; import de.rub.syssec.saaf.model.application.CodeLineInterface; import de.rub.syssec.saaf.model.application.SyntaxException; public class BasicBlock implements BasicBlockInterface { /** * A small helper class which we should get rid of. */ public static class FoundCodeLine { private final CodeLineInterface cl; private final int index; private final BasicBlockInterface bb; public FoundCodeLine(CodeLineInterface cl, BasicBlockInterface bb, int index) { this.cl = cl; this.index = index; this.bb = bb; } /** * * @return the cl found in the BB */ public CodeLineInterface getCodeLine() { return cl; } /** * @return the index of the cl in the BB where it was found */ public int getIndex() { return index; } /** * Get the BB where the found CL is located to. * @return the corresponding BB */ public BasicBlockInterface getBasicBlock() { return bb; } } private boolean hasReturn = false; private boolean hasThrow = false; private boolean hasGoto = false; private boolean hasDeadCode = false; private boolean isTryBlock = false; private boolean isCatchBlock = false; private final LinkedList<CodeLineInterface> codeLines; private final Method method; private boolean isSwitchTable = false; private LinkedList<BasicBlockInterface> nextBlocks = new LinkedList<BasicBlockInterface>(); private LinkedList<BasicBlockInterface> previousBlocks = new LinkedList<BasicBlockInterface>(); private LinkedList<Link> nextLinkBlocks = new LinkedList<Link>(); private LinkedList<Link> previousLinkBlocks = new LinkedList<Link>(); // The label assigned by a DFS private int label = -1; public LinkedList<Link> getNextLinkBlocks() { return nextLinkBlocks; } public void addNextLinkBlocks(Link link) { if (!nextLinkBlocks.contains(link)) nextLinkBlocks.add(link); } public LinkedList<Link> getPreviousLinkBlocks() { return previousLinkBlocks; } public void addPreviousLinkBlocks(Link link) { if (!previousLinkBlocks.contains(link)) previousLinkBlocks.add(link); } /** * A BasicBlock. * * @param codeLines * for this BB * @param method * the method where this BB belongs to */ public BasicBlock(LinkedList<CodeLineInterface> codeLines, Method method) { this.codeLines = codeLines; this.method = method; } /** * Return previous BBs. */ public LinkedList<BasicBlockInterface> getPreviousBB() { return previousBlocks; } /** * * @return the list of BBs reachable from this BB */ public LinkedList<BasicBlockInterface> getNextBB() { return nextBlocks; } /** * This method adds a bb to the list of previous BBs * * @param bb * - the bb to add to the list of previous BBs */ public void addPreviousBB(BasicBlockInterface bb) { if (!previousBlocks.contains(bb)) previousBlocks.add(bb); } /** * This method adds a bb to the list of following BBs * * @param bb * - the bb to add to the list of BBs following this BB */ public void addNextBB(BasicBlockInterface bb) { // multiple following BBs are possible (case, if-else, exceptions, ...) if (!(nextBlocks.contains(bb))) nextBlocks.add(bb); } /** * This method determines if a line is in the scope of this BB * * @param nr * - the line number to try * @return true if the given line nr. is in this BB */ public boolean containsLineNr(int nr) { if (this.getCodeLines().getLast().getLineNr() >= nr && this.getCodeLines().getFirst().getLineNr() <= nr) return true; else return false; } /** * * @return true if this BB is a switchTable */ public boolean isSwitchTable() { return isSwitchTable; } /** * * @return the codelines this BB consists of */ public LinkedList<CodeLineInterface> getCodeLines() { return codeLines; } /** * @return the method this BB belongs to */ public Method getMethod() { return method; } /** * * @param b * - indicate if this BB is a switch table or not */ public void setSwitchTable(boolean b) { isSwitchTable = b; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((codeLines == null) ? 0 : codeLines.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; BasicBlockInterface other = (BasicBlockInterface) obj; if (codeLines == null) { if (other.getCodeLines() != null) return false; } else if (!codeLines.equals(other.getCodeLines())) return false; return true; } /** * * @param target * - the target CodeLine to search for * @return true, if the target line is contained within this BB */ public boolean containsLine(String target) { for (CodeLineInterface cl : codeLines) { if (new String(cl.getLine()).equals(target)) return true; } return false; } /** * Get the unique label of this BB within a Method. * * @return the label of this BB */ public int getLabel() { return label; } /** * Set the unique label of this BB within a Method. * * @param label * - the label of this BB */ public void setLabel(int label) { this.label = label; } public String getUniqueId() { StringBuilder sb = new StringBuilder(); sb.append(getMethod().getSmaliClass().getUniqueId()); sb.append(','); sb.append(getMethod().getLabel()); sb.append(','); sb.append(label); return sb.toString(); } public boolean hasReturn(){ return hasReturn; } public boolean hasThrow(){ return hasThrow; } public boolean hasGoto(){ return hasGoto; } public void setHasReturn(boolean hasReturn){ this.hasReturn = hasReturn; } public void setHasThrow(boolean hasThrow) { this.hasThrow = hasThrow; } public void setHasGoto(boolean hasGoto) { this.hasGoto = hasGoto; } public void setHasDeadCode(boolean hasDeadCode){ this.hasDeadCode = hasDeadCode; } /** * Checks whether the BB contains dead code. This most likely results when someone * patches a program in order to return something and skip real code. BBs are build * after jmp/goto label/targets and this should not occur in "normal" apps. * * @return true if the BB contains a return opcode which is not the last opcode. */ public boolean hasDeadCode() { return hasDeadCode; } @Override public boolean isTryBlock(){ return isTryBlock; } @Override public boolean isCatchBlock() { return isCatchBlock; } @Override public void setIsTryBlock(boolean isTryBlock) { this.isTryBlock = isTryBlock; } @Override public void setIsCatchBlock(boolean isCatchBlock) { this.isCatchBlock = isCatchBlock; } /** * Get the previous CodeLine which contains actual code (is not empty, a comment etc). * @param bb the BB in which to search * @param index the current index, not previous CodeLine index * @return the corresponding CodeLine or a SyntaxException * @throws SyntaxException if no real instruction can be found in the given BB */ public static BasicBlock.FoundCodeLine getPreviousCodeLine(BasicBlockInterface bb, int index) throws SyntaxException { while (index >= 1) { index--; CodeLineInterface cl = bb.getCodeLines().get(index); if (!cl.isCode()) continue; else { return new BasicBlock.FoundCodeLine(cl, bb, index); } } throw new SyntaxException("Could not find previous \"real\" instruction in given BB!"); } /** * Returns the last codeline which contains a real opcode (not comment, dot prefix etc) from the BB. * @param bb the BasicBlock to search * @return the found line * @throws SyntaxException if now line was found */ public static BasicBlock.FoundCodeLine getLastCodeLine(BasicBlockInterface bb) throws SyntaxException { return BasicBlock.getPreviousCodeLine(bb, bb.getCodeLines().size()); } public String toString(){ StringBuilder bb = new StringBuilder(); for (CodeLineInterface cl: this.getCodeLines()){ bb.append(cl.getNrAndLine()); bb.append("\n"); } return bb.toString(); } }