/* Soot - a J*va Optimization Framework * Copyright (C) 2004 Ondrej Lhotak * * This library 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., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ package soot; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.StringTokenizer; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import soot.options.Options; /** Provides utility methods to retrieve an input stream for a class name, given * a classfile, or jimple or baf output files. */ public class SourceLocator { public SourceLocator( Singletons.Global g ) {} public static SourceLocator v() { return G.v().soot_SourceLocator(); } protected Set<ClassLoader> additionalClassLoaders = new HashSet<ClassLoader>(); protected Set<String> classesToLoad; /** Given a class name, uses the soot-class-path to return a ClassSource for the given class. */ public ClassSource getClassSource(String className) { if(classesToLoad==null) { classesToLoad = new HashSet<String>(); classesToLoad.addAll(Scene.v().getBasicClasses()); for(SootClass c: Scene.v().getApplicationClasses()) { classesToLoad.add(c.getName()); } } if( classPath == null ) { classPath = explodeClassPath(Scene.v().getSootClassPath()); } if( classProviders == null ) { setupClassProviders(); } for (ClassProvider cp : classProviders) { ClassSource ret = cp.find(className); if( ret != null ) return ret; } for(final ClassLoader cl: additionalClassLoaders) { ClassSource ret = new ClassProvider() { public ClassSource find(String className) { String fileName = className.replace('.', '/') + ".class"; InputStream stream = cl.getResourceAsStream(fileName); if(stream==null) return null; return new CoffiClassSource(className, stream); } }.find(className); if( ret != null ) return ret; } if(className.startsWith("soot.rtlib.tamiflex.")) { String fileName = className.replace('.', '/') + ".class"; InputStream stream = getClass().getClassLoader().getResourceAsStream(fileName); if(stream!=null) { return new CoffiClassSource(className, stream); } } return null; } public void additionalClassLoader(ClassLoader c) { additionalClassLoaders.add(c); } private void setupClassProviders() { classProviders = new LinkedList<ClassProvider>(); classProviders.add(new CoffiClassProvider()); } private List<ClassProvider> classProviders; public void setClassProviders( List<ClassProvider> classProviders ) { this.classProviders = classProviders; } private List<String> classPath; public List<String> classPath() { return classPath; } public void invalidateClassPath() { classPath = null; } private List<String> sourcePath; public List<String> sourcePath() { if( sourcePath == null ) { sourcePath = new ArrayList<String>(); for (String dir : classPath) { if( !isJar(dir) ) sourcePath.add(dir); } } return sourcePath; } private boolean isJar(String path) { File f = new File(path); if(f.isFile() && f.canRead()) { if(path.endsWith("zip") || path.endsWith("jar")) { return true; } else { G.v().out.println("Warning: the following soot-classpath entry is not a supported archive file (must be .zip or .jar): " + path); } } return false; } public List<String> getClassesUnder(String aPath) { List<String> fileNames = new ArrayList<String>(); if (isJar(aPath)) { List inputExtensions = new ArrayList(2); inputExtensions.add(".class"); inputExtensions.add(".jimple"); inputExtensions.add(".java"); try { ZipFile archive = new ZipFile(aPath); for (Enumeration entries = archive.entries(); entries.hasMoreElements(); ) { ZipEntry entry = (ZipEntry) entries.nextElement(); String entryName = entry.getName(); int extensionIndex = entryName.lastIndexOf('.'); if (extensionIndex >= 0) { String entryExtension = entryName.substring(extensionIndex); if (inputExtensions.contains(entryExtension)) { entryName = entryName.substring(0, extensionIndex); entryName = entryName.replace('/', '.'); fileNames.add(entryName); } } } } catch(IOException e) { G.v().out.println("Error reading " + aPath + ": " + e.toString()); throw new CompilationDeathException(CompilationDeathException.COMPILATION_ABORTED); } } else { File file = new File(aPath); File[] files = file.listFiles(); if (files == null) { files = new File[1]; files[0] = file; } for (File element : files) { if (element.isDirectory()) { List<String> l = getClassesUnder( aPath + File.separatorChar + element.getName()); Iterator<String> it = l.iterator(); while (it.hasNext()) { String s = it.next(); fileNames.add(element.getName() + "." + s); } } else { String fileName = element.getName(); if (fileName.endsWith(".class")) { int index = fileName.lastIndexOf(".class"); fileNames.add(fileName.substring(0, index)); } if (fileName.endsWith(".jimple")) { int index = fileName.lastIndexOf(".jimple"); fileNames.add(fileName.substring(0, index)); } if (fileName.endsWith(".java")) { int index = fileName.lastIndexOf(".java"); fileNames.add(fileName.substring(0, index)); } } } } return fileNames; } public String getFileNameFor(SootClass c, int rep) { if (rep == Options.output_format_none) return null; StringBuffer b = new StringBuffer(); if( !Options.v().output_jar() ) { b.append(getOutputDir()); } if ((b.length() > 0) && (b.charAt(b.length() - 1) != File.separatorChar)) b.append(File.separatorChar); if (rep != Options.output_format_dava) { if(rep == Options.output_format_class) { b.append(c.getName().replace('.', File.separatorChar)); } else if(rep == Options.output_format_template) { b.append(c.getName().replace('.', '_')); b.append("_Maker"); } else { b.append(c.getName()); } b.append(getExtensionFor(rep)); return b.toString(); } return getDavaFilenameFor(c, b); } private String getDavaFilenameFor(SootClass c, StringBuffer b) { b.append("dava"); b.append(File.separatorChar); { String classPath = b.toString() + "classes"; File dir = new File(classPath); if (!dir.exists()) try { dir.mkdirs(); } catch (SecurityException se) { G.v().out.println("Unable to create " + classPath); throw new CompilationDeathException(CompilationDeathException.COMPILATION_ABORTED); } } b.append("src"); b.append(File.separatorChar); String fixedPackageName = c.getJavaPackageName(); if (fixedPackageName.equals("") == false) { b.append(fixedPackageName.replace('.', File.separatorChar)); b.append(File.separatorChar); } { String path = b.toString(); File dir = new File(path); if (!dir.exists()) try { dir.mkdirs(); } catch (SecurityException se) { G.v().out.println("Unable to create " + path); throw new CompilationDeathException(CompilationDeathException.COMPILATION_ABORTED); } } b.append(c.getShortJavaStyleName()); b.append(".java"); return b.toString(); } /* This is called after sootClassPath has been defined. */ public Set<String> classesInDynamicPackage(String str) { HashSet<String> set = new HashSet<String>(0); StringTokenizer strtok = new StringTokenizer( Scene.v().getSootClassPath(), String.valueOf(File.pathSeparatorChar)); while (strtok.hasMoreTokens()) { String path = strtok.nextToken(); // For jimple files List<String> l = getClassesUnder(path); for (String filename : l) { if (filename.startsWith(str)) set.add(filename); } // For class files; path = path + File.pathSeparatorChar; StringTokenizer tokenizer = new StringTokenizer(str, "."); while (tokenizer.hasMoreTokens()) { path = path + tokenizer.nextToken(); if (tokenizer.hasMoreTokens()) path = path + File.pathSeparatorChar; } l = getClassesUnder(path); for (String string : l) set.add(str + "." + string); } return set; } public String getExtensionFor(int rep) { switch (rep) { case Options.output_format_baf: return ".baf"; case Options.output_format_b: return ".b"; case Options.output_format_jimple: return ".jimple"; case Options.output_format_jimp: return ".jimp"; case Options.output_format_shimple: return ".shimple"; case Options.output_format_shimp: return ".shimp"; case Options.output_format_grimp: return ".grimp"; case Options.output_format_grimple: return ".grimple"; case Options.output_format_class: return ".class"; case Options.output_format_dava: return ".java"; case Options.output_format_jasmin: return ".jasmin"; case Options.output_format_xml: return ".xml"; case Options.output_format_template: return ".java"; default: throw new RuntimeException(); } } public String getOutputDir() { String ret = Options.v().output_dir(); if( ret.length() == 0 ) ret = "sootOutput"; File dir = new File(ret); if (!dir.exists()) { try { if( !Options.v().output_jar() ) { dir.mkdirs(); } } catch (SecurityException se) { G.v().out.println("Unable to create " + ret); throw new CompilationDeathException(CompilationDeathException.COMPILATION_ABORTED); } } return ret; } /** Explodes a class path into a list of individual class path entries. */ protected List<String> explodeClassPath( String classPath ) { List<String> ret = new ArrayList<String>(); StringTokenizer tokenizer = new StringTokenizer(classPath, File.pathSeparator); while( tokenizer.hasMoreTokens() ) { String originalDir = tokenizer.nextToken(); String canonicalDir; try { canonicalDir = new File(originalDir).getCanonicalPath(); ret.add(canonicalDir); } catch( IOException e ) { throw new CompilationDeathException( "Couldn't resolve classpath entry "+originalDir+": "+e ); } } return ret; } public static class FoundFile { FoundFile( ZipFile zipFile, ZipEntry entry ) { this.zipFile = zipFile; this.entry = entry; } FoundFile( File file ) { this.file = file; } public File file; public ZipFile zipFile; public ZipEntry entry; public InputStream inputStream() { try { if( file != null ) return new FileInputStream(file); return doJDKBugWorkaround(zipFile.getInputStream(entry), entry.getSize()); } catch( IOException e ) { throw new RuntimeException( "Caught IOException "+e ); } } } private static InputStream doJDKBugWorkaround(InputStream is, long size) throws IOException { int sz = (int) size; byte[] buf = new byte[sz]; final int N = 1024; int ln = 0; int count = 0; while (sz > 0 && (ln = is.read(buf, count, Math.min(N, sz))) != -1) { count += ln; sz -= ln; } return new ByteArrayInputStream(buf); } /** Searches for a file with the given name in the exploded classPath. */ public FoundFile lookupInClassPath( String fileName ) { for (String dir : classPath) { FoundFile ret; if(isJar(dir)) { ret = lookupInJar(dir, fileName); } else { ret = lookupInDir(dir, fileName); } if( ret != null ) return ret; } return null; } private FoundFile lookupInDir(String dir, String fileName) { File f = new File( dir+File.separatorChar+fileName ); if( f.canRead() ) { return new FoundFile(f); } return null; } private FoundFile lookupInJar(String jar, String fileName) { try { ZipFile jarFile = new ZipFile(jar); ZipEntry entry = jarFile.getEntry(fileName); if( entry == null ) return null; return new FoundFile(jarFile, entry); } catch( IOException e ) { throw new RuntimeException( "Caught IOException "+e+" looking in jar file "+jar+" for file "+fileName ); } } private HashMap<String, String> sourceToClassMap; public HashMap<String, String> getSourceToClassMap(){ return sourceToClassMap; } public void setSourceToClassMap(HashMap<String, String> map){ sourceToClassMap = map; } public void addToSourceToClassMap(String key, String val) { sourceToClassMap.put(key, val); } /** Returns the name of the class in which the (possibly inner) class * className appears. */ public String getSourceForClass( String className ) { String javaClassName = className; if (className.indexOf("$") != -1) { // class is an inner class and will be in // Outer of Outer$Inner javaClassName = className.substring(0, className.indexOf("$")); //System.out.println("cut off inner class: look for: "+javaClassName); } // always do this because an inner class could be in a class // thats in the map if (sourceToClassMap != null) { //System.out.println("in source map: "+sourceToClassMap); if (sourceToClassMap.get(javaClassName) != null) { javaClassName = sourceToClassMap.get(javaClassName); } } return javaClassName; } }