/*
* This file is part of JOP, the Java Optimized Processor
* see <http://www.jopdesign.com/>
*
* Copyright (C) 2005-2008, Martin Schoeberl (martin@jopdesign.com)
*
* 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/>.
*/
/*
* Created on 04.06.2005
*
*/
package com.jopdesign.common.tools;
import com.jopdesign.common.ClassInfo;
import com.jopdesign.common.MethodInfo;
import com.jopdesign.common.graphutils.ClassVisitor;
import com.jopdesign.common.logger.LogConfig;
import com.jopdesign.common.misc.JavaClassFormatError;
import com.jopdesign.common.type.ClassRef;
import com.jopdesign.common.type.ConstantMethodInfo;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.generic.CPInstruction;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.util.InstructionFinder;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.Priority;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Find a correct order of static class initializers (<clinit>)
*
* @author martin
*/
public class ClinitOrder implements ClassVisitor {
// TODO maybe do this the other way round? (define clinitName,.. in Config or something,
// use Config.clinitName... in ClinitOrder; but this way it is easier to remember ..)
public static final String clinitName = "<clinit>";
public static final String clinitDesc = "()V";
public static final String clinitSig = "<clinit>()V";
private static final Logger logger = Logger.getLogger(LogConfig.LOG_STRUCT + ".ClinitOrder");
private Map<ClassInfo, Set<ClassInfo>> clinit = new HashMap<ClassInfo, Set<ClassInfo>>();
public ClinitOrder() {
}
@Override
public boolean visitClass(ClassInfo classInfo) {
MethodInfo mi = classInfo.getMethodInfo(clinitSig);
if (mi != null) {
Set<ClassInfo> depends = findDependencies(mi, false);
clinit.put(classInfo, depends);
}
return true;
}
@Override
public void finishClass(ClassInfo classInfo) {
}
private Set<ClassInfo> findDependencies(MethodInfo method, boolean inRec) {
// System.out.println("find dep. in "+cli.clazz.getClassName()+":"+mi.getMethod().getName());
Set<ClassInfo> depends = new HashSet<ClassInfo>();
if (method.isNative() || method.isAbstract()) {
// nothing to do
// or should we look for all possible subclasses on
// abstract.... or in general also all possible
// subclasses???? :-(
return depends;
}
ClassInfo classInfo = method.getClassInfo();
ConstantPoolGen cpoolgen = method.getConstantPoolGen();
InstructionList il = method.getCode().getInstructionList();
InstructionFinder f = new InstructionFinder(il);
// TODO can we encounter an empty instruction list?
//if(il.getStart() == null) {
// return depends;
//}
// find instructions that access the constant pool
// collect all indices to constants in ClassInfo
String cpInstr = "CPInstruction";
for (Iterator it = f.search(cpInstr); it.hasNext();) {
InstructionHandle[] match = (InstructionHandle[]) it.next();
InstructionHandle first = match[0];
CPInstruction ii = (CPInstruction) first.getInstruction();
int idx = ii.getIndex();
Constant co = cpoolgen.getConstant(idx);
ClassRef classRef = null;
Set addDepends = null;
ClassInfo clinfo;
MethodInfo minfo;
switch (co.getTag()) {
case Constants.CONSTANT_Class:
classRef = classInfo.getConstantInfo(co).getClassRef();
clinfo = classRef.getClassInfo();
if (clinfo != null) {
minfo = clinfo.getMethodInfo("<init>()V");
if (minfo != null) {
addDepends = findDependencies(minfo, true);
}
}
// check for all sub classes when no going up the hierarchy
// if (!inRec) {
// Object[] y = clinfo.getSubClasses().toArray();
// for (int i=0; i<y.length; ++i) {
// clinfo = (ClassInfo) y[i];
// minfo = clinfo.getMethodInfo("<init>()V");
// if (minfo!=null) {
// System.out.println("known sub classes with this method");
// System.out.println(((ClassInfo) y[i]).clazz.getClassName());
// }
// }
// }
break;
case Constants.CONSTANT_Fieldref:
case Constants.CONSTANT_InterfaceMethodref:
classRef = classInfo.getConstantInfo(co).getClassRef();
break;
case Constants.CONSTANT_Methodref:
ConstantMethodInfo mref = (ConstantMethodInfo) classInfo.getConstantInfo(co);
classRef = mref.getClassRef();
minfo = mref.getMethodRef().getMethodInfo();
if (minfo != null) {
addDepends = findDependencies(minfo, true);
}
// check for all sub classes when no going up the hierarchy
// if (!inRec) {
// Object[] x = clinfo.getSubClasses().toArray();
// for (int i=0; i<x.length; ++i) {
// clinfo = (ClassInfo) x[i];
// minfo = clinfo.getMethodInfo(sigstr);
// if (minfo!=null) {
// System.out.println("known sub classes with this method");
// System.out.println(((ClassInfo) x[i]).clazz.getClassName());
// }
// }
// }
break;
}
if (classRef != null) {
ClassInfo clinf = classRef.getClassInfo();
if (clinf != null) {
if (clinf.getMethodInfo(clinitSig) != null) {
// don't add myself as dependency
if (!clinf.equals(method.getClassInfo())) {
depends.add(clinf);
}
}
}
}
if (addDepends != null) {
for (Object addDepend : addDepends) {
ClassInfo addCli = (ClassInfo) addDepend;
if (addCli.equals(method.getClassInfo())) {
throw new JavaClassFormatError("cyclic indirect <clinit> dependency");
}
depends.add(addCli);
}
}
}
return depends;
}
/**
* Print the dependency for debugging. Not used at the moment.
* @param warn if true, print with warning level
*/
private void printDependency(boolean warn) {
Priority lvl = warn ? Level.WARN : Level.DEBUG;
Set<ClassInfo> cliSet = clinit.keySet();
for (ClassInfo clinf : cliSet) {
logger.log(lvl, "Class " + clinf.getClassName());
Set<ClassInfo> depends = clinit.get(clinf);
for (ClassInfo clf : depends) {
logger.log(lvl, "\tdepends " + clf.getClassName());
}
}
}
/**
* Find a 'correct' oder for the static <clinit>.
* Throws an error on cyclic dependencies.
*
* @return the ordered list of classes
* @throws JavaClassFormatError if a cyclic dependency has been found.
*/
public List<ClassInfo> findOrder() {
printDependency(false);
Set<ClassInfo> cliSet = clinit.keySet();
List<ClassInfo> order = new LinkedList<ClassInfo>();
int maxIter = cliSet.size();
// maximum loop bound detects cyclic dependency
for (int i = 0; i < maxIter && cliSet.size() != 0; ++i) {
Iterator itCliSet = cliSet.iterator();
while (itCliSet.hasNext()) {
ClassInfo clinf = (ClassInfo) itCliSet.next();
Set<ClassInfo> depends = clinit.get(clinf);
if (depends.size() == 0) {
order.add(clinf);
// check all depends sets and remove the added
// element (a leave in the dependent tree
for (ClassInfo clinfInner : clinit.keySet()) {
Set<ClassInfo> dep = clinit.get(clinfInner);
dep.remove(clinf);
}
itCliSet.remove();
}
}
}
if (cliSet.size() != 0) {
printDependency(true);
throw new JavaClassFormatError("Cyclic dependency in <clinit>");
}
return order;
}
}