/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This 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 software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ant.tasks.retrocheck;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.Descriptor;
import javassist.bytecode.FieldInfo;
import javassist.bytecode.MethodInfo;
/**
* takes jar or class files and checks the weave the bytecode
*
* @author <a href="mailto:bill@jboss.org">Bill Burke</a>
* @author <a href="mailto:adrian@jboss.org">Adrian Brock</a>
* @version $Revision: 1.3 $
*/
public class Checker
{
private static final String INIT = "<init>";
private static final String CLINIT = "<clinit>";
private FileFilter classFileFilter = new FileFilter()
{
public boolean accept(File pathname)
{
return pathname.getName().endsWith(".class");
}
};
private FileFilter directoryFilter = new FileFilter()
{
public boolean accept(File pathname)
{
return pathname.isDirectory();
}
};
public boolean verbose = false;
public boolean suppress = true;
public boolean isJarFile(File src)
{
boolean isJarFile = false;
if( src.isFile() )
{
String name = src.getName().toLowerCase();
isJarFile = name.endsWith(".jar") || name.endsWith(".zip");
}
return isJarFile;
}
public static void main(String[] args) throws Exception
{
long start = System.currentTimeMillis();
Checker checker = new Checker();
try
{
checker.check(args);
}
catch (Exception e)
{
throw e;
}
System.out.println("Check Successful: " + (System.currentTimeMillis() - start) + " ms");
}
public void usage()
{
System.err.println("Usage: RetroCheck [-cp <classpath>] [-classpath <classpath>] [-verbose] <dir>+");
}
// Make public and static so that transformers can locate it to do work
// transformers may generate class files and they need to determine
// file locations and such. This will also be used as a flag to tell
// transformers whether they are in compile or load-time mode.
public static URLClassLoader loader;
public void check(String[] args) throws Exception
{
if (args.length == 0)
{
usage();
System.exit(1);
return;
}
ArrayList<URL> paths = new ArrayList<URL>();
ArrayList<File> files = new ArrayList<File>();
for (int i = 0; i < args.length; i++)
{
if (args[i].equals("-verbose"))
{
verbose = true;
continue;
}
else if (args[i].equals("-suppress"))
{
suppress = true;
continue;
}
else if (args[i].equals("-cp") || args[i].equals("-classpath"))
{
if (i + 1 > args.length - 1)
{
usage();
System.exit(1);
return;
}
++i;
StringTokenizer tokenizer = new StringTokenizer(args[i], File.pathSeparator);
while (tokenizer.hasMoreTokens())
{
String cpath = tokenizer.nextToken();
File f = new File(cpath);
paths.add(f.toURL());
}
continue;
}
else if (args[i].equals("--SOURCEPATH"))
{
addFilesFromSourcePathFile(files, args[++i]);
continue;
}
File f = new File(args[i]).getCanonicalFile();
files.add(f);
}
URL[] urls = paths.toArray(new URL[paths.size()]);
loader = new URLClassLoader(urls, Thread.currentThread().getContextClassLoader());
Thread.currentThread().setContextClassLoader(loader);
String classpath = System.getProperty("java.class.path", ".");
String pathSeparator = System.getProperty("path.separator");
StringTokenizer st = new StringTokenizer(classpath, pathSeparator);
paths.clear();
while (st.hasMoreTokens())
{
String cpath = st.nextToken();
File f = new File(cpath);
paths.add(f.toURL());
}
urls = paths.toArray(new URL[paths.size()]);
ClassLoader poolLoader = new URLClassLoader(urls, EmptyClassLoader.EMPTY);
ClassPool pool = new ClassPool(false);
pool.appendClassPath(new CheckerClassPath(poolLoader));
pool.appendClassPath(new ClassClassPath(Object.class));
//Add all the classes to compile
for (File f : files)
{
if (f.isDirectory())
{
addDirectory(f, pool);
}
else if (classFileFilter.accept(f))
{
addFile(f, pool);
}
else
{
if (verbose)
System.out.println("[retrocheck] " + f + " is neither a java class or a directory");
}
}
int failed = 0;
//Check each class
for (CheckClassInfo info : classesToCheck.values())
failed += checkFile(info);
if (failed > 0)
{
System.out.println("Total errors/warnings: " + failed);
System.exit(1);
}
}
private HashMap<String, CheckClassInfo> classesToCheck = new HashMap<String, CheckClassInfo>();
private void addDirectory(File dir, ClassPool pool) throws Exception
{
File[] classFiles = dir.listFiles(classFileFilter);
for (File classFile : classFiles)
addFile(classFile, pool);
File[] directories = dir.listFiles(directoryFilter);
for (File directory : directories)
addDirectory(directory, pool);
}
private void addFile(File file, ClassPool pool) throws Exception
{
ClassFile cf = createClassFile(file);
CtClass clazz = pool.get(cf.getName());
String className = cf.getName();
String srcRoot = file.getCanonicalPath();
srcRoot = srcRoot.substring(0, srcRoot.length() - className.length() - 6);
CheckClassInfo info = new CheckClassInfo(file, srcRoot, clazz);
classesToCheck.put(className, info);
}
private ClassFile createClassFile(final File file) throws Exception
{
DataInputStream is = new DataInputStream(new FileInputStream(file));
ClassFile cf = new ClassFile(is);
is.close();
return cf;
}
private void addFilesFromSourcePathFile(ArrayList<File> files, String sourcePathFile)
{
BufferedReader reader = null;
try
{
reader = new BufferedReader(new FileReader(new File(sourcePathFile).getCanonicalFile()));
String fileName = reader.readLine();
while (fileName != null)
{
files.add(new File(fileName).getCanonicalFile());
fileName = reader.readLine();
}
}
catch (Exception e)
{
try
{
reader.close();
}
catch (IOException e1)
{
}
throw new RuntimeException(e);
}
}
public int checkFile(CheckClassInfo info) throws Exception
{
if (info.isCompiled())
return 0;
if( verbose )
System.out.println("[checkFile] " + info.getClassName());
URL classUrl = loader.getResource(info.getClassName().replace('.', '/') + ".class");
if (classUrl == null)
{
System.out.println("[warning] Unable to find " + info.getFile()
+ " within classpath. Make sure all transforming classes are within classpath.");
return 1;
}
File classUrlFile = new File(URLDecoder.decode(classUrl.getFile(), "UTF-8"));
File infoFile = new File(URLDecoder.decode(info.getFile().toString(), "UTF-8"));
if (classUrlFile.equals(infoFile) == false)
{
System.out.println("[warning] Trying to compile " + info.getFile() + " and found it also within "
+ classUrl.getFile() + " will not proceed. ");
return 1;
}
int failed = doCheck(loader, info);
info.setCompiled(true);
if (verbose)
{
if (failed == 0)
System.out.println("[ok] " + info.getClassName());
}
return failed;
}
public int doCheck(ClassLoader cl, CheckClassInfo info)
{
CtClass clazz = info.getClazz();
ClassFile file = clazz.getClassFile();
ClassPool pool = clazz.getClassPool();
int failed = 0;
// Incorrect major version
int major = file.getMajorVersion();
if (major > 48)
{
if (failed == 0)
System.out.println("==== " + info.getFile());
System.out.println("Wrong major version " + major);
++failed;
}
// Check the super class exists
String superClassName = file.getSuperclass();
try
{
pool.get(superClassName);
}
catch (NotFoundException e)
{
if (failed == 0)
System.out.println("==== " + info.getFile());
System.out.println("SuperClass not found " + superClassName);
++failed;
}
// Check the interfaces exist
String[] intfs = file.getInterfaces();
for (String intf : intfs)
{
try
{
pool.get(intf);
}
catch (NotFoundException e)
{
if (failed == 0)
System.out.println("==== " + info.getFile());
System.out.println("Interface not found " + intf);
++failed;
}
}
// Check the field types
List<FieldInfo> fields = (List<FieldInfo>) file.getFields();
for (FieldInfo field : fields)
{
String name = field.getName();
String typeName = Descriptor.toJavaName(field.getDescriptor());
try
{
CtField ctField = clazz.getField(name);
ctField.getType();
}
catch (NotFoundException e)
{
if (failed == 0)
System.out.println("==== " + info.getFile());
System.out.println("Class not found " + typeName + " for field " + name);
++failed;
}
}
// Check the method types
List<MethodInfo> methods = (List<MethodInfo>) file.getMethods();
for (MethodInfo method : methods)
{
String name = method.getName();
String descriptor = method.getDescriptor();
if (CLINIT.equals(name))
continue;
try
{
if (INIT.equals(name) == false)
{
CtMethod ctMethod = clazz.getMethod(name, descriptor);
ctMethod.getReturnType();
}
}
catch (NotFoundException e)
{
if (failed == 0)
System.out.println("==== " + info.getFile());
System.out.println("Return type not found for method " + name + "." + descriptor);
++failed;
}
if (INIT.equals(name))
{
try
{
CtConstructor ctConstructor = clazz.getConstructor(descriptor);
ctConstructor.getParameterTypes();
}
catch (NotFoundException e)
{
if (failed == 0)
System.out.println("==== " + info.getFile());
System.out.println("Cannot find constructor parameter types " + name + "." + descriptor);
++failed;
}
try
{
CtConstructor ctConstructor = clazz.getConstructor(descriptor);
ctConstructor.getExceptionTypes();
}
catch (NotFoundException e)
{
if (failed == 0)
System.out.println("==== " + info.getFile());
System.out.println("Cannot find constructor exception types " + name + "." + descriptor);
++failed;
}
}
else
{
try
{
CtMethod ctMethod = clazz.getMethod(name, descriptor);
ctMethod.getParameterTypes();
}
catch (NotFoundException e)
{
if (failed == 0)
System.out.println("==== " + info.getFile());
System.out.println("Cannot find method parameter types " + name + "." + descriptor);
++failed;
}
try
{
CtMethod ctMethod = clazz.getMethod(name, descriptor);
ctMethod.getExceptionTypes();
}
catch (NotFoundException e)
{
if (failed == 0)
System.out.println("==== " + info.getFile());
System.out.println("Cannot find method exception types " + name + "." + descriptor);
++failed;
}
}
}
// Now we've checked the signatures of the class, let's look at the references
file.compact();
ConstPool consts = file.getConstPool();
for (int i = 1; i < consts.getSize(); ++i) // Yes start at 1
{
switch (consts.getTag(i))
{
case ConstPool.CONST_Fieldref:
{
String type = consts.getFieldrefClassName(i);
String name = consts.getFieldrefName(i);
try
{
CtClass ctClazz = pool.get(type);
ctClazz.getField(name);
}
catch (NotFoundException e)
{
if (failed == 0)
System.out.println("==== " + info.getFile());
System.out.println("Cannot find field " + type + "." + name);
++failed;
}
break;
}
case ConstPool.CONST_InterfaceMethodref:
{
String type = consts.getInterfaceMethodrefClassName(i);
String name = consts.getInterfaceMethodrefName(i);
String descriptor = consts.getInterfaceMethodrefType(i);
try
{
CtClass ctClazz = pool.get(type);
ctClazz.getMethod(name, descriptor);
}
catch (NotFoundException e)
{
if (failed == 0)
System.out.println("==== " + info.getFile());
System.out.println("Cannot find interface method " + type + "." + name + descriptor);
++failed;
}
break;
}
case ConstPool.CONST_Methodref:
{
String type = consts.getMethodrefClassName(i);
String name = consts.getMethodrefName(i);
String descriptor = consts.getMethodrefType(i);
if (INIT.equals(name))
{
try
{
CtClass ctClazz = pool.get(type);
ctClazz.getConstructor(descriptor);
}
catch (NotFoundException e)
{
if (failed == 0)
System.out.println("==== " + info.getFile());
System.out.println("Cannot find constructor " + type + descriptor);
++failed;
}
}
else
{
try
{
CtClass ctClazz = pool.get(type);
ctClazz.getMethod(name, descriptor);
}
catch (NotFoundException e)
{
if (failed == 0)
System.out.println("==== " + info.getFile());
System.out.println("Cannot find method " + type + "." + name + descriptor);
++failed;
}
}
break;
}
}
}
if (failed > 0)
{
System.out.println(failed + " errors for " + info.getFile());
System.out.println();
}
// Return the result
return failed;
}
private class CheckClassInfo
{
File file;
String srcRoot;
String className;
CtClass clazz;
boolean compiled;
CheckClassInfo(File file, String srcRoot, CtClass clazz)
{
this.file = file;
this.srcRoot = srcRoot;
this.className = clazz.getName();
this.clazz = clazz;
}
public File getFile()
{
return file;
}
public String getSrcRoot()
{
return srcRoot;
}
public boolean isCompiled()
{
return compiled;
}
public void setCompiled(boolean compiled)
{
this.compiled = compiled;
}
public String getClassName()
{
return className;
}
public CtClass getClazz()
{
return clazz;
}
public void setClazz(CtClass clazz)
{
this.clazz = clazz;
}
}
}