/** * Sets the leaving RET instruction. Must be invoked after all instructions are added. Must not * be invoked for top-level 'subroutine'. */ void setLeavingRET() { if (localVariable == UNSET) { throw new AssertionViolatedException( "setLeavingRET() called for top-level 'subroutine' or forgot to set local variable first."); } Iterator iter = instructions.iterator(); InstructionHandle ret = null; while (iter.hasNext()) { InstructionHandle actual = (InstructionHandle) iter.next(); if (actual.getInstruction() instanceof RET) { if (ret != null) { throw new StructuralCodeConstraintException( "Subroutine with more then one RET detected: '" + ret + "' and '" + actual + "'."); } else { ret = actual; } } } if (ret == null) { throw new StructuralCodeConstraintException("Subroutine without a RET detected."); } if (((RET) ret.getInstruction()).getIndex() != localVariable) { throw new StructuralCodeConstraintException( "Subroutine uses '" + ret + "' which does not match the correct local variable '" + localVariable + "'."); } theRET = ret; }
/** * A utility method that calculates the successors of a given InstructionHandle <B>in the same * subroutine</B>. That means, a RET does not have any successors as defined here. A * JsrInstruction has its physical successor as its successor (opposed to its target) as defined * here. */ private static InstructionHandle[] getSuccessors(InstructionHandle instruction) { final InstructionHandle[] empty = new InstructionHandle[0]; final InstructionHandle[] single = new InstructionHandle[1]; final InstructionHandle[] pair = new InstructionHandle[2]; Instruction inst = instruction.getInstruction(); if (inst instanceof RET) { return empty; } // Terminates method normally. if (inst instanceof ReturnInstruction) { return empty; } // Terminates method abnormally, because JustIce mandates // subroutines not to be protected by exception handlers. if (inst instanceof ATHROW) { return empty; } // See method comment. if (inst instanceof JsrInstruction) { single[0] = instruction.getNext(); return single; } if (inst instanceof GotoInstruction) { single[0] = ((GotoInstruction) inst).getTarget(); return single; } if (inst instanceof BranchInstruction) { if (inst instanceof Select) { // BCEL's getTargets() returns only the non-default targets, // thanks to Eli Tilevich for reporting. InstructionHandle[] matchTargets = ((Select) inst).getTargets(); InstructionHandle[] ret = new InstructionHandle[matchTargets.length + 1]; ret[0] = ((Select) inst).getTarget(); System.arraycopy(matchTargets, 0, ret, 1, matchTargets.length); return ret; } else { pair[0] = instruction.getNext(); pair[1] = ((BranchInstruction) inst).getTarget(); return pair; } } // default case: Fall through. single[0] = instruction.getNext(); return single; }
/** Adds a new JSR or JSR_W that has this subroutine as its target. */ public void addEnteringJsrInstruction(InstructionHandle jsrInst) { if ((jsrInst == null) || (!(jsrInst.getInstruction() instanceof JsrInstruction))) { throw new AssertionViolatedException("Expecting JsrInstruction InstructionHandle."); } if (localVariable == UNSET) { throw new AssertionViolatedException("Set the localVariable first!"); } else { // Something is wrong when an ASTORE is targeted that does not operate on the same local // variable than the rest of the // JsrInstruction-targets and the RET. // (We don't know out leader here so we cannot check if we're really targeted!) if (localVariable != ((ASTORE) (((JsrInstruction) jsrInst.getInstruction()).getTarget().getInstruction())) .getIndex()) { throw new AssertionViolatedException("Setting a wrong JsrInstruction."); } } theJSRs.add(jsrInst); }
/* * Satisfies Subroutine.getAccessedLocalIndices(). */ public int[] getAccessedLocalsIndices() { // TODO: Implement caching. HashSet<Integer> acc = new HashSet<Integer>(); if (theRET == null && this != TOPLEVEL) { throw new AssertionViolatedException( "This subroutine object must be built up completely before calculating accessed locals."); } Iterator i = instructions.iterator(); while (i.hasNext()) { InstructionHandle ih = (InstructionHandle) i.next(); // RET is not a LocalVariableInstruction in the current version of BCEL. if (ih.getInstruction() instanceof LocalVariableInstruction || ih.getInstruction() instanceof RET) { int idx = ((IndexedInstruction) (ih.getInstruction())).getIndex(); acc.add(new Integer(idx)); // LONG? DOUBLE?. try { // LocalVariableInstruction instances are typed without the need to look into // the constant pool. if (ih.getInstruction() instanceof LocalVariableInstruction) { int s = ((LocalVariableInstruction) ih.getInstruction()).getType(null).getSize(); if (s == 2) acc.add(new Integer(idx + 1)); } } catch (RuntimeException re) { throw new AssertionViolatedException( "Oops. BCEL did not like NULL as a ConstantPoolGen object."); } } } int[] ret = new int[acc.size()]; i = acc.iterator(); int j = -1; while (i.hasNext()) { j++; ret[j] = ((Integer) i.next()).intValue(); } return ret; }
/** * Constructor. * * @param il A MethodGen object representing method to create the Subroutine objects of. */ public Subroutines(MethodGen mg) { InstructionHandle[] all = mg.getInstructionList().getInstructionHandles(); CodeExceptionGen[] handlers = mg.getExceptionHandlers(); // Define our "Toplevel" fake subroutine. TOPLEVEL = new SubroutineImpl(); // Calculate "real" subroutines. HashSet<InstructionHandle> sub_leaders = new HashSet<InstructionHandle>(); // Elements: InstructionHandle for (int i = 0; i < all.length; i++) { Instruction inst = all[i].getInstruction(); if (inst instanceof JsrInstruction) { sub_leaders.add(((JsrInstruction) inst).getTarget()); } } // Build up the database. Iterator iter = sub_leaders.iterator(); while (iter.hasNext()) { SubroutineImpl sr = new SubroutineImpl(); InstructionHandle astore = (InstructionHandle) (iter.next()); sr.setLocalVariable(((ASTORE) (astore.getInstruction())).getIndex()); subroutines.put(astore, sr); } // Fake it a bit. We want a virtual "TopLevel" subroutine. subroutines.put(all[0], TOPLEVEL); sub_leaders.add(all[0]); // Tell the subroutines about their JsrInstructions. // Note that there cannot be a JSR targeting the top-level // since "Jsr 0" is disallowed in Pass 3a. // Instructions shared by a subroutine and the toplevel are // disallowed and checked below, after the BFS. for (int i = 0; i < all.length; i++) { Instruction inst = all[i].getInstruction(); if (inst instanceof JsrInstruction) { InstructionHandle leader = ((JsrInstruction) inst).getTarget(); ((SubroutineImpl) getSubroutine(leader)).addEnteringJsrInstruction(all[i]); } } // Now do a BFS from every subroutine leader to find all the // instructions that belong to a subroutine. HashSet<InstructionHandle> instructions_assigned = new HashSet<InstructionHandle>(); // we don't want to assign an instruction to two or more // Subroutine objects. Hashtable<InstructionHandle, Color> colors = new Hashtable< InstructionHandle, Color>(); // Graph colouring. Key: InstructionHandle, Value: java.awt.Color . iter = sub_leaders.iterator(); while (iter.hasNext()) { // Do some BFS with "actual" as the root of the graph. InstructionHandle actual = (InstructionHandle) (iter.next()); // Init colors for (int i = 0; i < all.length; i++) { colors.put(all[i], Color.white); } colors.put(actual, Color.gray); // Init Queue ArrayList<InstructionHandle> Q = new ArrayList<InstructionHandle>(); Q.add(actual); // add(Obj) adds to the end, remove(0) removes from the start. /* BFS ALGORITHM MODIFICATION: Start out with multiple "root" nodes, as exception handlers are starting points of top-level code, too. [why top-level? TODO: Refer to the special JustIce notion of subroutines.]*/ if (actual == all[0]) { for (int j = 0; j < handlers.length; j++) { colors.put(handlers[j].getHandlerPC(), Color.gray); Q.add(handlers[j].getHandlerPC()); } } /* CONTINUE NORMAL BFS ALGORITHM */ // Loop until Queue is empty while (Q.size() != 0) { InstructionHandle u = (InstructionHandle) Q.remove(0); InstructionHandle[] successors = getSuccessors(u); for (int i = 0; i < successors.length; i++) { if (((Color) colors.get(successors[i])) == Color.white) { colors.put(successors[i], Color.gray); Q.add(successors[i]); } } colors.put(u, Color.black); } // BFS ended above. for (int i = 0; i < all.length; i++) { if (colors.get(all[i]) == Color.black) { ((SubroutineImpl) (actual == all[0] ? getTopLevel() : getSubroutine(actual))) .addInstruction(all[i]); if (instructions_assigned.contains(all[i])) { throw new StructuralCodeConstraintException( "Instruction '" + all[i] + "' is part of more than one subroutine (or of the top level and a subroutine)."); } else { instructions_assigned.add(all[i]); } } } if (actual != all[0]) { // If we don't deal with the top-level 'subroutine' ((SubroutineImpl) getSubroutine(actual)).setLeavingRET(); } } // Now make sure no instruction of a Subroutine is protected by exception handling code // as is mandated by JustIces notion of subroutines. for (int i = 0; i < handlers.length; i++) { InstructionHandle _protected = handlers[i].getStartPC(); while (_protected != handlers[i] .getEndPC() .getNext()) { // Note the inclusive/inclusive notation of "generic API" exception // handlers! Enumeration subs = subroutines.elements(); while (subs.hasMoreElements()) { Subroutine sub = (Subroutine) subs.nextElement(); if (sub != subroutines.get(all[0])) { // We don't want to forbid top-level exception handlers. if (sub.contains(_protected)) { throw new StructuralCodeConstraintException( "Subroutine instruction '" + _protected + "' is protected by an exception handler, '" + handlers[i] + "'. This is forbidden by the JustIce verifier due to its clear definition of subroutines."); } } } _protected = _protected.getNext(); } } // Now make sure no subroutine is calling a subroutine // that uses the same local variable for the RET as themselves // (recursively). // This includes that subroutines may not call themselves // recursively, even not through intermediate calls to other // subroutines. noRecursiveCalls(getTopLevel(), new HashSet<Integer>()); }