/* Copyright (c) 2002-2011 by XMLVM.org * * Project Info: http://www.xmlvm.org * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library 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 Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ package org.xmlvm.refcount; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.jdom.Attribute; import org.jdom.DataConversionException; import org.jdom.Element; import org.jdom.Namespace; import org.xmlvm.refcount.optimizations.DeferredNullingOptimization; import org.xmlvm.refcount.optimizations.RefCountOptimization; import org.xmlvm.refcount.optimizations.RegisterSizeAndNullingOptimization; /** * Overview: * * This class uses reference counting to simulate the effects of a JVM garbage * collector. It operates on the output of the DEX XMLVM process which is a XML * based representation of a compiled java class that has been transformed from * a stack machine to a register machine. This class manages object lifespan * using a reference counting approach. For example, when an object is created * it is given a reference count of 1. During the lifespan of the object the * reference count is incremented as other objects store away pointers to the * object in question. Eventually, the reference count is set to zero and the * memory for the object is released. * * This class is interesting because manages these reference counts without any * client programmer intervention to simulate the effects of a garbage. This is * done by following a few simple rules: 1) Each pointer to an object always has * a 'retain' associated with it. For example, if a register variable points * toward an object then the following pattern would occur: Register r1 = * object1; [r1 retain]; * * When the pointer's end of life occurred, we would release the object * reference [r1 release]; r1 = null; * * The same concept holds for other classes of pointer including, arrays, static * references to objects, as well as instance references. In general the * following invariant always holds true: * * If there is a pointer to an object, than that pointer has associated with it * an increment for the objects reference count. If that pointer is overwritten * for any reason, that reference is released. * * Based on this invariant, we know that when we can no longer point to an * object all of its reference count increments will be gone and hence it will * be freed by the runtime. * * Usage: For a <dex:method/> represented as a jdom.Element, call Process. */ public class ReferenceCounting { Namespace dex= InstructionProcessor.dex; Namespace vm= InstructionProcessor.vm; String tmpRegNameSuffix= "tmp"; /** * The entry point to this class. This function takes a method element and * processes it, adding instructions to release and retain objects as * needed. For the command set that it adds, see the InstructionProcessor * class. */ @SuppressWarnings("unchecked") public void process(Element method) throws DataConversionException, ReferenceCountingException { Attribute isAbstract= method.getAttribute("isAbstract"); Attribute isNative= method.getAttribute("isNative"); // abstract and native methods do not require processing if (isAbstract != null && isAbstract.getBooleanValue()) { return; } if (isNative != null && isNative.getBooleanValue()) { return; } Element codeElement= method.getChild("code", dex); int numReg= codeElement.getAttribute("register-size").getIntValue(); processRecStart(numReg, (List<Element>) codeElement.getChildren(), codeElement); } /** * Set the expected frees that we must do before any optimizations have * removed them. */ private void setWillFree(Map<Element, InstructionActions> beenTo) throws ReferenceCountingException, DataConversionException { { for (Map.Entry<Element, InstructionActions> e : beenTo.entrySet()) { RegisterSet objectRegs= e.getValue().getObjectRegs(); if (!e.getValue().getConflict().isEmpty()) { throw new ReferenceCountingException("Ambigious register contents possible: Conflict: " + e.getValue().getConflict()); } InstructionUseInfo useInfo= e.getValue().useInfo; RegisterSet toFree; if (e.getKey().getName().startsWith("return")) { // we want to free everything except what this instruction // uses. toFree= objectRegs.andNot(useInfo.usedReg()); } else { // we free any register reference that is overwritten by // this // instruction toFree= objectRegs.and(useInfo.allWrites()); } useInfo.willFree= toFree; useInfo.willNull= toFree.clone(); } } } /** * This is the last step in the release/retain markup process. It processes * all of the DEX instructions that we have traversed in a method, looking * at how they have been marked up. Based on how they have been marked up it * adds required release/retains or other commands to the body of the method * being processed. * * Returns whether this method needs to have a temp register defined. */ private boolean processReleaseRetain(Map<Element, InstructionActions> beenTo) throws ReferenceCountingException, DataConversionException { boolean needsTmpReg= false; for (Map.Entry<Element, InstructionActions> e : beenTo.entrySet()) { if (!e.getValue().getConflict().isEmpty()) { throw new ReferenceCountingException("Ambigious register contents possible: Conflict: " + e.getValue().getConflict()); } InstructionUseInfo useInfo= e.getValue().useInfo; // if we are writing into an object, we may need to free. // All objects in registers are held with a reference, so we will // need to release. List<Element> toAddBefore= new ArrayList<Element>(); List<Element> toAddAfter= new ArrayList<Element>(); // Release last -- because other wise we can get into odd situations // where we don't to a required retain before the release. List<Element> toReleaseLast= new ArrayList<Element>(); RegisterSet toFree; toFree= useInfo.willFree; // for the registers we want to free for (int oneReg : toFree) { // If we use the object in the instruction as an argument and // overwrite it, we must be careful to preserve it until after // the call is done. // Example of true case // tmp = f1; // f1 = func(f1); // [release tmp]; // Example of false case: // f1 = func(f1); // [release f1] if (!useInfo.usesAsObj().and(useInfo.allWrites()).isEmpty()) { if (useInfo.freeTmpAfter) { throw new ReferenceCountingException("Conflict, tmp register used twice."); } Element tmpR= new Element(InstructionProcessor.cmd_tmp_equals_r, vm); tmpR.setAttribute("reg", oneReg + ""); toAddBefore.add(tmpR); needsTmpReg= true; Element releaseTmp= new Element(InstructionProcessor.cmd_release, vm); releaseTmp.setAttribute("reg", tmpRegNameSuffix); toReleaseLast.add(releaseTmp); Element nullTmp= new Element(InstructionProcessor.cmd_set_null, vm); nullTmp.setAttribute("num", tmpRegNameSuffix); toReleaseLast.add(nullTmp); } else { // No need to use tmp Element release= new Element(InstructionProcessor.cmd_release, vm); release.setAttribute("reg", oneReg + ""); toAddBefore.add(release); if (useInfo.willNull.has(oneReg)) { Element nullTmp= new Element(InstructionProcessor.cmd_set_null, vm); nullTmp.setAttribute("num", oneReg + ""); toAddBefore.add(nullTmp); } } } if (useInfo.putRelease != null) { if (!useInfo.usesAsObj().and(useInfo.allWrites()).isEmpty()) { needsTmpReg= true; throw new ReferenceCountingException("We do not handle the case where a release is " + "made in a x = foo(x) situation because it " + " hasn't showed up so far"); } else { toAddBefore.add(useInfo.putRelease); } } // Add any necessary retains. for (int oneReg : useInfo.requiresRetain) { Element retain= new Element(InstructionProcessor.cmd_retain, vm); retain.setAttribute("reg", oneReg + ""); toAddAfter.add(retain); } // This handles the case where xmlvm2objc.xsl has set the temp reg // to a value because a function call was made, but the result was // not used by the program. if (useInfo.freeTmpAfter) { Element releaseTmp= new Element(InstructionProcessor.cmd_release, vm); releaseTmp.setAttribute("reg", tmpRegNameSuffix); toAddAfter.add(releaseTmp); needsTmpReg= true; } toAddAfter.addAll(toReleaseLast); // At this point toAddBefore and toAddAfter have been filled with // whatever instructions we need to add before and after this // specific element. The helper function adds them. addBeforeAndAfter(e.getKey(), toAddBefore, toAddAfter); } return needsTmpReg; } /** * This is here because the jdom XML API is dumb enough that it cannot * easily find the element before element X, or the element after element X. * * This function adds some elements before and after a particular element. * * TODO: if we believe prevElement map and nextElement map are correct, then * we can use them instead to make this run faster. */ @SuppressWarnings("unchecked") void addBeforeAndAfter(Element toAddTo, List<Element> toAddBefore, List<Element> toAddAfter) throws ReferenceCountingException { Element parent= toAddTo.getParentElement(); List<Object> con= parent.getContent(); // go through the parents elements looking for this element for (int x= 0; x < con.size(); x++) { if (con.get(x).equals(toAddTo)) { // order here matters so we don't screw up the index for the // before add. parent.addContent(x + 1, toAddAfter); parent.addContent(x, toAddBefore); return; } } throw new ReferenceCountingException("Impossible"); } /** * label id to label element. Used for construction of code paths. */ Map<Integer, Element> labels= new HashMap<Integer, Element>(); /** * What is the next and previous element for a particular element ? */ Map<Element, Element> nextElement= new HashMap<Element, Element>(); Map<Element, Element> prevElement= new HashMap<Element, Element>(); /** * Represents a single run of the reference counter. We have this because we * currently use a two pass implementation and don't want any interactions * between the passes. * * TODO: We could prevent having to do a whole lot of work in the second * pass if we care to. */ class RunState { public List<CodePath> allCodePaths= new ArrayList<CodePath>(); /* * What we label the next code path as */ public int codePathId= 0; /* * List of all the elements that we have visited in the method. */ public Map<Element, InstructionActions> beenTo= new HashMap<Element, InstructionActions>(); /* * What are the conflicted registers on this run? */ public RegisterSet allConflict= RegisterSet.none(); /* * Used so we don't have to use stack recursion, which apparently causes * big problems in the JVM. */ LinkedList<OneRecusiveCall> callsToDo= new LinkedList<OneRecusiveCall>(); } /* * Our current run context. TODO: pass this down the stack instead of having * it be an instance variable. */ RunState curRun; /** * This adds any labels it finds to our labels map. It also populates our * previous and next element hashes. */ private void addToNextPrevElement(List<Element> toProcess) throws DataConversionException { Element prev= null; for (int k= 0; k < toProcess.size(); k++) { Element cur= toProcess.get(k); if (cur.getName().equals("label")) { labels.put(cur.getAttribute("id").getIntValue(), cur); } if (prev != null) { nextElement.put(prev, cur); prevElement.put(cur, prev); } prev= cur; } } @SuppressWarnings("unchecked") private void processRecStart(int numReg, List<Element> toProcess, Element codeElement) throws DataConversionException, ReferenceCountingException { addToNextPrevElement(toProcess); for (Element x : toProcess) { if (x.getName().equals("try-catch")) { // Try catches are special: we must descend into them to // mark up their code. addToNextPrevElement(x.getChild("try", dex).getChildren()); for (Element catchE : (List<Element>) x.getChildren("catch", dex)) { addToNextPrevElement(catchE.getChildren()); } } } doMarkup(toProcess); curRun.allConflict= RegisterSet.none(); for (Entry<Element, InstructionActions> x : curRun.beenTo.entrySet()) { curRun.allConflict.orEq(x.getValue().getConflict()); } // the method element. Element parent= toProcess.get(0).getParentElement(); // We found some code paths that end up with a register that has an // object OR a primitive at a point where we think we need to do a // object release. This is bad, so we must split the register so that // the primitive is always separate from the object. if (!curRun.allConflict.isEmpty()) { int newRegSize= splitConflictedRegisters(numReg, curRun.allConflict, curRun.beenTo); parent.getAttribute("register-size").setValue(newRegSize + ""); doMarkup(toProcess); } refLog("Conflict is: " + curRun.allConflict); setWillFree(curRun.beenTo); // Start going through optimizations before generating change RefCountOptimization.ReturnValue ret= new RegisterSizeAndNullingOptimization().Process(curRun.allCodePaths, curRun.beenTo, codeElement); new DeferredNullingOptimization().Process(curRun.allCodePaths, curRun.beenTo, codeElement); // TODO fix this optimization // new ExcessRetainsOptimization().Process(curRun.allCodePaths, // curRun.beenTo, codeElement); toProcess.addAll(0, ret.functionInit); addExTempReg(toProcess); clearReleaseRetainOnSyntheticMembers(curRun, codeElement); // Now we want to follow the paths to find unambiguous ones so that we // can determine // where to do release/retain to prevent ambiguity. // we do this by tracking which branch we are on by explicitly // constructing paths through the code during // our normal traversal. boolean usesTemp= processReleaseRetain(curRun.beenTo); if (usesTemp) { Element setupTmp= new Element(InstructionProcessor.cmd_define_register, InstructionProcessor.vm); setupTmp.setAttribute("vartype", InstructionProcessor.cmd_define_register_attr_temp); toProcess.add(0, setupTmp); } } /* * Synthetics help create cycles so we don't do releases or retains on them. */ @SuppressWarnings("unchecked") private void clearReleaseRetainOnSyntheticMembers(RunState curRun, Element codeElement) throws DataConversionException { // Find the synthetic members of the class; Element classElement= codeElement.getParentElement().getParentElement(); HashSet<String> hashSet= new HashSet<String>(); for (Element elem : (List<Element>) classElement.getChildren()) { if (elem.getName().equals("field") && elem.getAttribute("isSynthetic") != null && elem.getAttributeValue("isSynthetic").equals("true") && elem.getAttributeValue("name").startsWith("this$")) { hashSet.add(elem.getAttributeValue("name")); } } for (Map.Entry<Element, InstructionActions> e : curRun.beenTo.entrySet()) { String instructionElementName= e.getKey().getName(); if ((instructionElementName.equals("iput-object") || instructionElementName.equals("iput")) && e.getKey().getAttribute("member-name") != null && hashSet.contains(e.getKey().getAttributeValue("member-name"))) { InstructionUseInfo useInfo= e.getValue().useInfo; // We don't want to release what was in there because it was not // retained useInfo.putRelease= null; useInfo.requiresRetain= RegisterSet.none(); } } } /** * Adds definition for exception register if needed. */ private void addExTempReg(List<Element> toProcess) { boolean useEx= false; boolean useTmp= false; for (Element e : curRun.beenTo.keySet()) { if (e.getName().equals("throw") || e.getName().equals("try-catch")) { useEx= true; } if (useEx && useTmp) { break; // early quit } } if (useEx) { Element setupEx= new Element(InstructionProcessor.cmd_define_register, InstructionProcessor.vm); setupEx.setAttribute("vartype", InstructionProcessor.cmd_define_register_attr_exception); toProcess.add(0, setupEx); } for (Element e : curRun.beenTo.keySet()) { if (e.getName().startsWith("return")) { e.setAttribute("catchesException", useEx + ""); } } } /** * Determines conflicts and retain/release for the method. */ private void doMarkup(List<Element> toProcess) throws DataConversionException { // create a new run of the processor, prime the recursion, and then // run it until its complete. curRun= new RunState(); processRecAdd(RegisterSet.none(), RegisterSet.none(), toProcess.get(0), createNewCodePath(null)); processWhileCallsToDo(); // Debug print for state at this stage. //printInstSeq(toProcess); } /* * Purely for debug, shows the instructions as well as our what we have * calculated about them. */ @SuppressWarnings("unchecked") private void printInstSeq(List<Element> toProcess) { refLog("All " + toProcess.size() + " instructions been to " + curRun.beenTo.size()); for (Element x : toProcess) { if (curRun.beenTo.containsKey(x)) { String startStr= curRun.beenTo.get(x).useInfo + ""; if (x.getName().equals("label")) { refLog(startStr + " ID = " + x.getAttributeValue("id")); } else { refLog(startStr + ""); } if (x.getName().equals("try-catch")) { printInstSeq(x.getChild("try", dex).getChildren()); } } } } /** * In certain cases, DEX will create a code path where we think we need to * do a release of an object on a particular register which may or may not * hold an object depending on the particular path through the code taken at * runtime. There are several ways to approach this issue, the most simple * is to split a conflicted register into two new registers. Conceptually, * this is done by defining a function that maps RX to RY or RZ depending on * whether RX is known to hold an object or a primitive. * * The following code implements this mapping, with the slight optimization * that instead of mapping RX to RY and RZ, it maps it to RX and RY. This is * because it keeps the sequence of registers intact with no holes, and * because it allows us to deal with function parameters more easily. * * We return the total number of registers required for this method. */ int splitConflictedRegisters(int numReg, RegisterSet allConflict, Map<Element, InstructionActions> beenTo) throws DataConversionException, ReferenceCountingException { for (int reg : allConflict) { int newReg= numReg++; // When dealing with a passed parameter that has a conflict, we want // to make sure to not change the register that the parameter is // originally inserted into. int regObj= reg; int regNonObj= newReg; for (Element varE : beenTo.keySet()) { if (varE.getName().equals("var")) { InstructionUseInfo varUi= this.curRun.beenTo.get(varE).useInfo; if (varUi.isWrite) { if (!varUi.writesObj().isEmpty()) { regObj= reg; regNonObj= newReg; break; } else if (!varUi.writesNonObj().isEmpty()) { regObj= newReg; regNonObj= reg; } else { throw new ReferenceCountingException("impossible"); } } } } refLog(reg + " -> o:" + regObj + ":" + regNonObj); // Go through all the instructions in the method, replacing any // that use the conflicted value with the new register or the old // register depending on whether the instruction expects the // register to contain an object or a primitive. for (Map.Entry<Element, InstructionActions> beenToKv : beenTo.entrySet()) { InstructionUseInfo ui= beenToKv.getValue().useInfo; for (Map.Entry<Attribute, Boolean> kv : ui.typeIsObj.entrySet()) { if (kv.getKey().getIntValue() == reg) { if (kv.getValue()) { // its an object kv.getKey().setValue(regObj + ""); } else { kv.getKey().setValue(regNonObj + ""); } } } } } return numReg; } /** * Given a parent code path, create a child. */ private CodePath createNewCodePath(CodePath curPath) { CodePath c; c= new CodePath(curRun.codePathId++, curPath); curRun.allCodePaths.add(c); if (curPath != null) { curPath.subPaths.add(c); } return c; } /** * Class representing collected parameters for one execution of the body of * ProcessWhileCallsToDo */ class OneRecusiveCall { RegisterSet regHoldingObject; RegisterSet regNotHoldingObject; Element currentElement; CodePath codePath; } /** * Helper to add to the list of recursive calls to do. */ private void processRecAdd(RegisterSet regHoldingObject, RegisterSet regNotHoldingObject, Element currentElement, CodePath codePath) throws DataConversionException { OneRecusiveCall oneCall= new OneRecusiveCall(); oneCall.regHoldingObject= regHoldingObject; oneCall.regNotHoldingObject= regNotHoldingObject; oneCall.currentElement= currentElement; oneCall.codePath= codePath; this.curRun.callsToDo.add(oneCall); } /** * This function creates a representation of the different execution paths * through the method. At the same time, it gathers information on how * particular instructions are making use of registers. This is an * implementation of a recursive function, however when implemented as a * directly recursive function (without the calls to do) the JVM blows up * for lack of stack space. Because modifying stack space available to a * thread in the JVM (even if you create a new thread) is a pain, we just * switched to using a heap list for the recursive stack. * * In most cases this is *not* tail recursion, so don't try and make a loop * out of it. */ @SuppressWarnings("unchecked") private void processWhileCallsToDo() throws DataConversionException { int maxSize= 0; while (this.curRun.callsToDo.size() != 0) { maxSize= Math.max(maxSize, this.curRun.callsToDo.size()); OneRecusiveCall thisTime= this.curRun.callsToDo.removeFirst(); // Arguments to the recursive function. RegisterSet regHoldingObject= thisTime.regHoldingObject; RegisterSet regNotHoldingObject= thisTime.regNotHoldingObject; Element currentElement= thisTime.currentElement; CodePath codePath= thisTime.codePath; if (currentElement == null) { continue; // base case } InstructionActions actions= beenHereBefore(this.curRun.beenTo, regHoldingObject, regNotHoldingObject, currentElement, codePath); if (actions == null) { continue; // base case } InstructionUseInfo useInfo= actions.useInfo; Element nextInstruction; if (currentElement.getName().startsWith("goto")) { nextInstruction= labels.get(currentElement.getAttribute("target").getIntValue()); } else { nextInstruction= nextElement.get(currentElement); if (nextInstruction == null && currentElement.getParentElement().getName().equals("try")) { // Exited the try, move to the element after the // try terminates. nextInstruction= nextElement.get(currentElement.getParentElement().getParentElement()); } } // the ones we came in with RegisterSet ourObjUse= regHoldingObject.clone(); // plus the ones that we write obj to ourObjUse.orEq(useInfo.writesObj()); // minus the ones we write non obj into ourObjUse.andEqNot(useInfo.writesNonObj()); RegisterSet ourNonObjUse= regNotHoldingObject.clone(); ourNonObjUse.orEq(useInfo.writesNonObj()); ourNonObjUse.andEqNot(useInfo.writesObj()); if (currentElement.getName().startsWith("return")) { continue; } if (currentElement.getName().equals("try-catch")) { processRecAdd(ourObjUse, ourNonObjUse, (Element) currentElement.getChild("try", dex).getChildren().get(0), createNewCodePath(codePath)); for (Element caught : (List<Element>) currentElement.getChildren("catch", dex)) { Element nextInst= labels.get(caught.getAttribute("target").getIntValue()); processRecAdd(ourObjUse, ourNonObjUse, nextInst, createNewCodePath(codePath)); } } else if (currentElement.getName().equals("packed-switch") || currentElement.getName().equals("sparse-switch")) { processRecAdd(ourObjUse, ourNonObjUse, nextInstruction, createNewCodePath(codePath)); for (Element target : (List<Element>) currentElement.getChildren("case", dex)) { processRecAdd(ourObjUse, ourNonObjUse, labels.get(target.getAttribute("label").getIntValue()), createNewCodePath(codePath)); } } else if (currentElement.getName().startsWith("if")) { processRecAdd(ourObjUse, ourNonObjUse, nextInstruction, createNewCodePath(codePath)); processRecAdd(ourObjUse, ourNonObjUse, labels.get(currentElement.getAttribute("target").getIntValue()), createNewCodePath(codePath)); } else if (currentElement.getName().equals("label")) { // It will be useful in the future to have labels treated as // creating a new code path. processRecAdd(ourObjUse, ourNonObjUse, nextInstruction, createNewCodePath(codePath)); } else { // straight line code. processRecAdd(ourObjUse, ourNonObjUse, nextInstruction, codePath); } } refLog("Max recusrive depth " + maxSize); } /** * This thing is used to determine whether or not our recursion keeps going * It terminates the recursion if we have been to this instruction with the * exact same state before. We return information collected about the * current instruction to the caller. */ private static InstructionActions beenHereBefore(Map<Element, InstructionActions> beenTo, RegisterSet regHoldingObject, RegisterSet regNotHoldingObject, Element currentElement, CodePath c) throws DataConversionException { InstructionActions toRet; if (beenTo.containsKey(currentElement)) { // Visited here on another code branch toRet= beenTo.get(currentElement); } else { // Haven't been here yet. toRet= new InstructionActions(); toRet.useInfo= processElement(currentElement); beenTo.put(currentElement, toRet); } // Labels are not code, and thus do not belong in code paths. if (!currentElement.getName().equals("label")) { c.path.add(new OnePathInstructionRegisterContents(currentElement, regHoldingObject, regNotHoldingObject)); } boolean enteredNotHolding= false; // figure out if we have been here before with the same // enteredNotHolding state for (RegisterSet m : toRet.enteredNot) { if (m.equals(regNotHoldingObject)) { enteredNotHolding= true; break; } } boolean enteredHolding= false; // figure out if we have been here before with the same enteredHolding // state. for (RegisterSet m : toRet.enteredHoldingObj) { if (m.equals(regHoldingObject)) { enteredHolding= true; break; } } if (enteredNotHolding && enteredHolding) { // We were here before with the exact same state: time to terminate // the search along this path. return null; } // Add info about the state we were in when we got to here along this // code path. if (!enteredHolding) { toRet.enteredHoldingObj.add(regHoldingObject); } if (!enteredNotHolding) { toRet.enteredNot.add(regNotHoldingObject); } return toRet; } /** * This function creates a InstructionUseInfo based on the current element * TODO: if anyone really cares this can be made faster by not using * reflection. */ private static InstructionUseInfo processElement(Element element) throws DataConversionException { InstructionUseInfo use= new InstructionUseInfo(element); // If we find the instruction using the generic handler, return // immediately. if (InstructionProcessor.processGeneric(element, use)) { return use; } else { // Otherwise, we need to hit the correct processor function: String todo= "process_" + element.getName().replace("-", "_"); Method method; try { method= InstructionProcessor.class.getMethod(todo, Element.class, InstructionUseInfo.class); method.invoke(null, element, use); } catch (Exception ex) { throw new DataConversionException(ex.getMessage(), "When attempting to: " + todo); } } return use; } private static void refLog(String message) { // Log.debug("ref", message); } }